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

[フレーム]

Subscribe to: Post Comments (Atom)

AltStyle によって変換されたページ (->オリジナル) /