I recently had a lot of fun helping to optimize some RPC code that was using reflection to dynamically invoke methods in a C# application. Below are a list of implementations that we experimented with, and their performance.
- Directly Invoking the Method
 - Using MethodInfo.Invoke
 - Using Delegate.DynamicInvoke
 - Casting to a Func
 - Casting a Delegate to Dynamic
 
Spoilers: Here are the results. (The tests for this can be see below.)
| Name | First Call (Ticks) | Next Million Calls | Invoke Comparison | 
|---|---|---|---|
| Invoke | 1 | 39795 | - | 
| MethodInfo.Invoke | 12 | 967523 | x24 | 
| Delegate.DynamicInvoke | 32 | 1580086 | x39 | 
| Func Invoke | 731 | 41331 | x1 | 
| Dynamic Cast | 1126896 | 85495 | x2 | 
Conclusion: Invoking a method or delegate directly is always fastest, but when you need to execute code dynamically, then (after the first invoke) the dynamic invoke of a delegate is significantly faster than using reflection.

Let's take a look at the test code...
Unit Tests
public class InvokePerformance
{
public const int Iterations = 1000000;
public static readonly object[] Args = {1, true};
public static readonly TestClass Obj = new TestClass();
public static readonly Type Type = typeof(TestClass);
public static readonly MethodInfo Method = Type.GetMethod("TestMethod");
private readonly ITestOutputHelper _output;
    public InvokePerformance(ITestOutputHelper output)
    {
_output = output;
}
[Fact]
public void Invoke()
    {
var arg0 = (int) Args[0];
var arg1 = (bool) Args[1];
        var sw0 = Stopwatch.StartNew();
Method.Invoke(Obj, Args);
sw0.Stop();
        var sw1 = Stopwatch.StartNew();
for (var i = 0; i < Iterations; i++)
        {
Obj.TestMethod(arg0, arg1);
}
sw1.Stop();
_output.WriteLine(sw0.ElapsedTicks.ToString());
_output.WriteLine(sw1.ElapsedTicks.ToString());
}
[Fact]
public void MethodInfoInvoke()
    {
        var sw0 = Stopwatch.StartNew();
Method.Invoke(Obj, Args);
sw0.Stop();
        var sw1 = Stopwatch.StartNew();
for (var i = 0; i < Iterations; i++)
        {
Method.Invoke(Obj, Args);
}
sw1.Stop();
_output.WriteLine(sw0.ElapsedTicks.ToString());
_output.WriteLine(sw1.ElapsedTicks.ToString());
}
[Fact]
public void DelegateDynamicInvoke()
    {
var delType = Expression.GetDelegateType(typeof(int), typeof(bool), typeof(int));
        var del = Delegate.CreateDelegate(delType, Obj, Method);
        var sw0 = Stopwatch.StartNew();
del.DynamicInvoke(Args);
sw0.Stop();
        var sw1 = Stopwatch.StartNew();
for (var i = 0; i < Iterations; i++)
        {
del.DynamicInvoke(Args);
}
sw1.Stop();
_output.WriteLine(sw0.ElapsedTicks.ToString());
_output.WriteLine(sw1.ElapsedTicks.ToString());
}
[Fact]
public void FuncInvoke()
    {
var delType = Expression.GetDelegateType(typeof(int), typeof(bool), typeof(int));
var del = (Func<int, bool, int>) Delegate.CreateDelegate(delType, Obj, Method);
var arg0 = (int) Args[0];
var arg1 = (bool) Args[1];
        var sw0 = Stopwatch.StartNew();
del(arg0, arg1);
sw0.Stop();
        var sw1 = Stopwatch.StartNew();
for (var i = 0; i < Iterations; i++)
        {
del(arg0, arg1);
}
sw1.Stop();
_output.WriteLine(sw0.ElapsedTicks.ToString());
_output.WriteLine(sw1.ElapsedTicks.ToString());
}
[Fact]
public void DynamicCast()
    {
var delType = Expression.GetDelegateType(typeof(int), typeof(bool), typeof(int));
dynamic del = Delegate.CreateDelegate(delType, Obj, Method);
dynamic arg0 = Args[0];
dynamic arg1 = Args[1];
        var sw0 = Stopwatch.StartNew();
del(arg0, arg1);
sw0.Stop();
        var sw1 = Stopwatch.StartNew();
for (var i = 0; i < Iterations; i++)
        {
del(arg0, arg1);
}
sw1.Stop();
_output.WriteLine(sw0.ElapsedTicks.ToString());
_output.WriteLine(sw1.ElapsedTicks.ToString());
}
public class TestClass
    {
public int TestMethod(int i, bool b)
        {
            return i + (b ? 1 : 2);
}
}
}
Enjoy,
Tom
No comments:
Post a Comment