I'd say that about 99% of all the computation occurring in a GP is in the execution of individual specimens (i.e. evaluating their expression trees). If we add functions and constants that are going to be evaluated millions of times, then these functions and constants should be REALLY fast! Using reflection to discover the fields, properties and methods that can be used by the GP does give us the ability to change the supported functions on-the-fly, but that ability comes at a price.
Using reflection to access the values of a field or a property is done in the same amount of ticks as accessing the field or property directly from the object:
The code that was used to get the above statistics is this:
static void VariablesAndPropertiesTest()
{
int numRuns = 100 * 1000;
var val = 0.0;
Functions fn = new Functions();
FieldInfo numTwo = fn.GetType().GetField("constNumTwo");
PropertyInfo numOne = fn.GetType().GetProperty("ConstNumOne");
Console.WriteLine("Reflection field");
_stopwatch.Start();
for (int i = 0; i < numRuns; ++i)
{
val = (double)numTwo.GetValue(fn);
}
_stopwatch.Stop();
Console.WriteLine("Avg Ticks \t " + _stopwatch.ElapsedTicks / numRuns);
Console.WriteLine("Direct field");
_stopwatch.Start();
for (int i = 0; i < numRuns; ++i)
{
val = fn.constNumTwo;
}
_stopwatch.Stop();
Console.WriteLine("Avg Ticks \t " + _stopwatch.ElapsedTicks / numRuns);
Console.WriteLine("Reflection property");
_stopwatch.Start();
for (int i = 0; i < numRuns; ++i)
{
val = (double)numOne.GetValue(fn, null);
}
_stopwatch.Stop();
Console.WriteLine("Avg Ticks \t " + _stopwatch.ElapsedTicks / numRuns);
Console.WriteLine("Direct property");
_stopwatch.Start();
for (int i = 0; i < numRuns; ++i)
{
val = fn.ConstNumOne;
}
_stopwatch.Stop();
Console.WriteLine("Avg Ticks \t " + _stopwatch.ElapsedTicks / numRuns);
Console.WriteLine("Direct method");
_stopwatch.Start();
for (int i = 0; i < numRuns; ++i)
{
val = fn.GPConstNumZero();
}
_stopwatch.Stop();
Console.WriteLine("Avg Ticks \t " + _stopwatch.ElapsedTicks / numRuns);
}