Reputation: 18239
I posted this question earlier about dynamically compiling code in C#, and the answer has lead to another question.
One suggestion is that I use delegates, which I tried and they work well. However, they are benching at about 8.4 X slower than direct calls, which makes no sense.
What is wrong with this code?
My results, .Net 4.0, 64 bit, ran exe directly: 62, 514, 530
public static int Execute(int i) { return i * 2; }
private void button30_Click(object sender, EventArgs e)
{
CSharpCodeProvider foo = new CSharpCodeProvider();
var res = foo.CompileAssemblyFromSource(
new System.CodeDom.Compiler.CompilerParameters()
{
GenerateInMemory = true,
CompilerOptions = @"/optimize",
},
@"public class FooClass { public static int Execute(int i) { return i * 2; }}"
);
var type = res.CompiledAssembly.GetType("FooClass");
var obj = Activator.CreateInstance(type);
var method = type.GetMethod("Execute");
int i = 0, t1 = Environment.TickCount, t2;
//var input = new object[] { 2 };
//for (int j = 0; j < 10000000; j++)
//{
// input[0] = j;
// var output = method.Invoke(obj, input);
// i = (int)output;
//}
//t2 = Environment.TickCount;
//MessageBox.Show((t2 - t1).ToString() + Environment.NewLine + i.ToString());
t1 = Environment.TickCount;
for (int j = 0; j < 100000000; j++)
{
i = Execute(j);
}
t2 = Environment.TickCount;
MessageBox.Show("Native: " + (t2 - t1).ToString() + Environment.NewLine + i.ToString());
var func = (Func<int, int>) Delegate.CreateDelegate(typeof (Func<int, int>), method);
t1 = Environment.TickCount;
for (int j = 0; j < 100000000; j++)
{
i = func(j);
}
t2 = Environment.TickCount;
MessageBox.Show("Dynamic delegate: " + (t2 - t1).ToString() + Environment.NewLine + i.ToString());
Func<int, int> funcL = Execute;
t1 = Environment.TickCount;
for (int j = 0; j < 100000000; j++)
{
i = funcL(j);
}
t2 = Environment.TickCount;
MessageBox.Show("Delegate: " + (t2 - t1).ToString() + Environment.NewLine + i.ToString());
}
Upvotes: 4
Views: 5454
Reputation: 67080
It makes sense. Delegates are not function pointers. They imply type checking, security and a lot of other stuffs. They're more close to the speed of a virtual function call (see this post) even if the performance impact derives from something completely different.
For a good comparison of different invocation techniques (some of them not mentioned in the question) read this article.
Upvotes: 4
Reputation: 269338
As Hans mentions in the comments on your question, the Execute
method is so simple that it's almost certainly being inlined by the jitter in your "native" test.
So what you're seeing isn't a comparison between a standard method call and a delegate invocation, but a comparison between an inlined i * 2
operation and a delegate invocation. (And that i * 2
operation probably boils down to just a single machine instruction, about as fast as you can get.)
Make your Execute
methods a bit more complicated to prevent inlining (and/or do it with the MethodImplOptions.NoInlining
compiler hint); then you'll get a more realistic comparison between standard method calls and delegate invocations. Chances are that the difference will be negligible in most situations:
[MethodImpl(MethodImplOptions.NoInlining)]
static int Execute(int i) { return ((i / 63.53) == 34.23) ? -1 : (i * 2); }
public static volatile int Result;
private static void Main(string[] args)
{
const int iterations = 100000000;
{
Result = Execute(42); // pre-jit
var s = Stopwatch.StartNew();
for (int i = 0; i < iterations; i++)
{
Result = Execute(i);
}
s.Stop();
Console.WriteLine("Native: " + s.ElapsedMilliseconds);
}
{
Func<int, int> func;
using (var cscp = new CSharpCodeProvider())
{
var cp = new CompilerParameters { GenerateInMemory = true, CompilerOptions = @"/optimize" };
string src = @"public static class Foo { public static int Execute(int i) { return ((i / 63.53) == 34.23) ? -1 : (i * 2); } }";
var cr = cscp.CompileAssemblyFromSource(cp, src);
var mi = cr.CompiledAssembly.GetType("Foo").GetMethod("Execute");
func = (Func<int, int>)Delegate.CreateDelegate(typeof(Func<int, int>), mi);
}
Result = func(42); // pre-jit
var s = Stopwatch.StartNew();
for (int i = 0; i < iterations; i++)
{
Result = func(i);
}
s.Stop();
Console.WriteLine("Dynamic delegate: " + s.ElapsedMilliseconds);
}
{
Func<int, int> func = Execute;
Result = func(42); // pre-jit
var s = Stopwatch.StartNew();
for (int i = 0; i < iterations; i++)
{
Result = func(i);
}
s.Stop();
Console.WriteLine("Delegate: " + s.ElapsedMilliseconds);
}
}
Upvotes: 5