Sunday, August 21, 2016

Optimizing Dynamic Method Invokes in .NET

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.

  1. Directly Invoking the Method
  2. Using MethodInfo.Invoke
  3. Using Delegate.DynamicInvoke
  4. Casting to a Func
  5. 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

Real Time Web Analytics