Saturday, May 1, 2010

Genetic Programming: C# Reflection Performance Revisited

I noticed that I made some mistakes in my first test so I fixed them and I added another test. The results are not significantly different but let me demonstrate the changes: I added a dictionary that will contain the methodInfo for reach function which will allow me to Invoke the methods directly without looking them up by string.


class Program
{
static Dictionary<int, List<String>> _functions =
new Dictionary<int, List<string>>();
static Dictionary<int, List<MethodInfo>> _methods =
new Dictionary<int, List<MethodInfo>>();
static Dictionary<int, List<MethodBase>> _methodBases =
new Dictionary<int, List<MethodBase>>();
static Dictionary<int, List<Delegate>> _delegate =
new Dictionary<int, List<Delegate>>();

static Stopwatch _stopwatch = new Stopwatch();

static void Main(string[] args)
{
Console.WriteLine("Frequency = " + Stopwatch.Frequency);
DyanmicInvocationTest();
Console.WriteLine("Press any key to exit!");
Console.ReadKey();
}

static void DyanmicInvocationTest()
{
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<string>());
}

_functions[pi.Length].Add(method.Name);

if (!_methods.ContainsKey(pi.Length))
{
_methods.Add(pi.Length, new List<MethodInfo>());
}
_methods[pi.Length].Add(method);

if (!_methodBases.ContainsKey(pi.Length))
{
_methodBases.Add(pi.Length, new List<MethodBase>());
}
_methodBases[pi.Length].Add(method);

if (!_delegate.ContainsKey(pi.Length))
{
_delegate.Add(pi.Length, new List<Delegate>());
}

MemberInfo[] members = typeFn.GetMembers();
switch(pi.Length)
{
case 0:
_delegate[pi.Length].Add(
Delegate.CreateDelegate(
typeof(Functions.ZeroParam), fn, method));
break;
case 1:
_delegate[pi.Length].Add(
Delegate.CreateDelegate(
typeof(Functions.OneParam), fn, method));
break;
default:
break;
}
}
}
int numRuns = 100 * 1000;
long time = 0;

// Member
Console.WriteLine("*** Running dynamic member invoke test ***");
for (int i = 0; i < numRuns; ++i)
{
time += DynamicInvokeMember(fn);
_stopwatch.Reset();
}

Console.WriteLine("Avg dynamic member invoke = " + (double)time/(double)numRuns);
time = 0;

// Method
Console.WriteLine("*** Running dynamic method invoke test ***");
for (int i = 0; i < numRuns; ++i)
{
time += DynamicInvokeMethod(fn);
_stopwatch.Reset();
}

Console.WriteLine("Avg dynamic method invoke = " + (double)time / (double)numRuns);
time = 0;

// Method Base
Console.WriteLine("*** Running dynamic method base invoke test ***");
for (int i = 0; i < numRuns; ++i)
{
time += DynamicInvokeMethodBase(fn);
_stopwatch.Reset();
}

Console.WriteLine("Avg dynamic method base invoke = " + (double)time / (double)numRuns);
time = 0;

// Delegate
Console.WriteLine("*** Running dynamic delegate test ***");
for (int i = 0; i < numRuns; ++i)
{
time += DynamicInvokeDelegate(fn);
_stopwatch.Reset();
}

Console.WriteLine("Avg dynamic delegate invoke = " + (double)time / (double)numRuns);
time = 0;

// Normal
Console.WriteLine("*** Running normal invocation test ***");
for (int i = 0; i < numRuns; ++i)
{
time += NormalInvoke(fn);
_stopwatch.Reset();
}

Console.WriteLine("Average time normal = " + (double)time / (double)numRuns);
}

static Int64 DynamicInvokeMember(Functions fn)
{
Type typeFn = fn.GetType();

object[] zeroParam = new object[0];
object[] oneParam = new object[1] { 1.0 };

_stopwatch.Start();
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])
{

switch (key)
{
case 0:
typeFn.InvokeMember(function,
BindingFlags.Default | BindingFlags.InvokeMethod,
null,
fn,
zeroParam,
null);
break;
case 1:
typeFn.InvokeMember(function,
BindingFlags.Default | BindingFlags.InvokeMethod,
null,
fn,
oneParam,
null);
break;
default:
break;
}
}
}

_stopwatch.Stop();
return _stopwatch.ElapsedTicks;
}

static Int64 DynamicInvokeMethod(Functions fn)
{
Type typeFn = fn.GetType();
object[] zeroParam = new object[0];
object[] oneParam = new object[1]{1.0};


_stopwatch.Start();
foreach (int key in _methods.Keys)
{
//Console.WriteLine(
// String.Format("num param {0}, num fn {1}",
// key, _functions[key].Count));
foreach (MethodInfo method in _methods[key])
{

switch (key)
{
case 0:
method.Invoke(fn, zeroParam);
break;
case 1:
method.Invoke(fn, oneParam);
break;
default:
break;
}
}
}

_stopwatch.Stop();
return _stopwatch.ElapsedTicks;
}

static Int64 DynamicInvokeMethodBase(Functions fn)
{
Type typeFn = fn.GetType();
object[] zeroParam = new object[0];
object[] oneParam = new object[1] { 1.0 };

_stopwatch.Start();
foreach (int key in _methodBases.Keys)
{
//Console.WriteLine(
// String.Format("num param {0}, num fn {1}",
// key, _functions[key].Count));
foreach (MethodBase method in _methodBases[key])
{
switch (key)
{
case 0:
method.Invoke(fn, zeroParam);
break;
case 1:
method.Invoke(fn, oneParam);
break;
default:
break;
}
}
}

_stopwatch.Stop();
return _stopwatch.ElapsedTicks;
}

static Int64 DynamicInvokeDelegate(Functions fn)
{
Type typeFn = fn.GetType();
object[] zeroParam = new object[0];
object[] oneParam = new object[1] { 1.0 };

_stopwatch.Start();
foreach (int key in _delegate.Keys)
{
//Console.WriteLine(
// String.Format("num param {0}, num fn {1}",
// key, _functions[key].Count));
foreach (Delegate method in _delegate[key])
{
switch (key)
{
case 0:
method.DynamicInvoke(zeroParam);//.Invoke(fn, zeroParam);
break;
case 1:
method.DynamicInvoke(oneParam);
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;
}
}


The DynamicInvokeMethod directly invokes the method, rather than looking it up by string from the Type, which offers a pretty large performance increase. There is no change to the NormalInvoke method.

So the results for the run with calculations are as follows:
  • The normal method invocation took an average of 213 CPU Ticks.
  • The dynamic method invocation took on average about 272 CPU Ticks.
  • The dynamic member invocation took an average of 304 CPU Ticks.

The results for the run without calculations is:
  • The normal method invocation took an average of 2.52 CPU Ticks.
  • The dynamic method invocation took on average about 61.71 CPU Ticks.
  • The dynamic member invocation took an average of 97.93 CPU Ticks.


I found some resources from other people and their performance analysis:

Simon Lucas at the University of Essex:

Mattias Fagerlund's Coding Blog:

No comments:

Post a Comment