Optimizing Dynamic Method Invokes in .NET

Datetime:2016-08-22 22:17:53          Topic: .Net           Share

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





About List