First I wrote a class containing some functions:
class Functions
{
private readonly String _symbol;
public Functions()
{
_symbol = "INVALID";
}
public Functions(String symbol)
{
_symbol = symbol;
}
#region ConstNumbers
public double GPConstNumZero()
{
LongOperation();
return 0.0;
}
public double GPConstNumPI()
{
LongOperation();
return Math.PI;
}
#endregion
#region OneParamFunctions
public double GPSin(double val)
{
LongOperation();
return Math.Sin(val);
}
public double GPMACD(double period)
{
LongOperation();
// Do the MACD calculations here
return Double.MaxValue;
}
#endregion
#region TwoParameterFunctions
// N/A
#endregion
private void LongOperation()
{
// I'll explain what goes here a little later
}
}
Then I wrote my test program:
class Program
{
// A dictionary that will map functions to the number of parameters
// where the key is the number of parameters and the functions
// that have the same number of parameters are added in the list
// corresponding to the key.
static Dictionary> _functions =
new Dictionary>();
// A stopwatch for performance analysis
static Stopwatch _stopwatch = new Stopwatch();
static void Main(string[] args)
{
// Start the Dynamic Method Invocation Test
DyanmicInvocationTest();
Console.WriteLine("Press any key to exit!");
Console.ReadKey();
}
static void DyanmicInvocationTest()
{
// Get all the method information
Functions fn = new Functions();
Type typeFn = fn.GetType();
MethodInfo[] methods = typeFn.GetMethods();
foreach (MethodInfo method in methods)
{
if (method.Name.StartsWith("GP"))
{
ParameterInfo[] pi = method.GetParameters();
if (!_functions.ContainsKey(pi.Length))
{
_functions.Add(pi.Length, new List());
}
_functions[pi.Length].Add(method.Name);
}
}
// Setup the performance analysis
int numRuns = 100 * 1000;
long time = 0;
// Run the dynamic invocation performance test
Console.WriteLine("Running dynamic invocation performance test");
for (int i = 0; i < numRuns; ++i)
{
time += DynamicInvoke(fn);
_stopwatch.Reset();
}
Console.WriteLine("Average time dynamic = " + (double)time/(double)numRuns);
time = 0;
// Run the normal invocation performance test
Console.WriteLine("Running normal invocation performance test");
for (int i = 0; i < numRuns; ++i)
{
time += NormalInvoke(fn);
_stopwatch.Reset();
}
Console.WriteLine("Average time normal = " + (double)time / (double)numRuns);
}
static Int64 DynamicInvoke(Functions fn)
{
Type typeFn = fn.GetType();
foreach (int key in _functions.Keys)
{
//Console.WriteLine(
// String.Format("num param {0}, num fn {1}",
// key, _functions[key].Count));
foreach (String function in _functions[key])
{
_stopwatch.Start();
switch (key)
{
case 0:
typeFn.InvokeMember(function,
BindingFlags.Default | BindingFlags.InvokeMethod,
null,
fn,
new object[0],
null);
break;
case 1:
typeFn.InvokeMember(function,
BindingFlags.Default | BindingFlags.InvokeMethod,
null,
fn,
new object[1] { 1.0 },
null);
break;
default:
break;
}
_stopwatch.Stop();
}
}
return _stopwatch.ElapsedTicks;
}
static long NormalInvoke(Functions fn)
{
_stopwatch.Start();
fn.GPConstNumPI();
fn.GPConstNumZero();
fn.GPMACD(1.0);
fn.GPSin(1.0);
_stopwatch.Stop();
return _stopwatch.ElapsedTicks;
}
}
Earlier I didn't show you the LongOperation method of the Functions class because I wanted to explain what I was testing. Since remote method invocation can be expensive, I wanted to see when I was going to be hit the hardest if I'm going to do dynamic method invocation and I contrived two test cases:
- Invoke all the functions, but only return the constants inside them (e.g. when a lot of the data is pre-calculated or it already exists as constants).
- Invoke the methods dynamically.
- Invoke the methods directly.
- Invoke all the functions, do some calculations and return the constants inside them (e.g. when a lot of the data is being computed at run-time).
- Invoke the methods dynamically.
- Invoke the methods directly.
private void LongOperation()
{
for (double i = 0.0; i < 1000.0; ++i)
{
Math.Sqrt(i);
}
}
When running the functions with the calculations I found that the performance hit was about 50% (i.e. methods invoked dynamically through reflection were about 50% slower than the methods invoked directly).
The truly horrifying part was when I ran the functions without any calculations and the performance hit was 3,967%... yes, you read it right! Invoking methods through reflection is nearly a 4,000% slower if you're not doing any calculations, but simply returning constants. See the graph below:
So naturally those numbers are mortifying, but there is some hope on the horizon: I found a blog by Gerhard Stephan (no clue who he is) where he discusses how to get a 2000% performance increase over the methods invoked with reflection. I'm still trying to understand what he's doing, but for now it's not looking very promising.
Conclusion:
I try to have as much of the data pre-calculated as possible which reduces my calculation time during the evolution of the strategies and maximizes the evolution iterations, so reflection is not looking very promising for me. If I had a lot more calculations in my functions, then I might consider reflection as a decent alternative. For now I'll keep looking for any ways to get that performance increase and if I can't figure out something within the next couple of days I'll have to drop this idea.


No comments:
Post a Comment