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