I'm curious how performant the Expression.Compile is versus lambda expression in the code and versus direct method usage, and also direct method calls vs virtual method calls (pseudo code):
var foo = new Foo(); var iFoo = (IFoo)foo; foo.Bar(); iFoo.Bar(); (() => foo.Bar())(); (() => iFoo.Bar())(); Expression.Compile(foo, Foo.Bar)(); Expression.Compile(iFoo, IFoo.Bar)(); Expression.CompileToMethod(foo, Foo.Bar); Expression.CompileToMethod(iFoo, IFoo.Bar); MethodInfo.Invoke(foo, Foo.Bar); MethodInfo.Invoke(iFoo, IFoo.Bar);
I didn't find any answer, so here is the performance test:
using System; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using System.Reflection.Emit; namespace ExpressionTest { public interface IFoo { int Bar(); } public sealed class FooImpl : IFoo { public int Bar() { return 0; } } class Program { static void Main(string[] args) { var foo = new FooImpl(); var iFoo = (IFoo)foo; Func<int> directLambda = () => foo.Bar(); Func<int> virtualLambda = () => iFoo.Bar(); var compiledDirectCall = CompileBar(foo, asInterfaceCall: false); var compiledVirtualCall = CompileBar(foo, asInterfaceCall: true); var compiledArgDirectCall = CompileBar<FooImpl>(); var compiledArgVirtualCall = CompileBar<IFoo>(); var barMethodInfo = typeof(FooImpl).GetMethod(nameof(FooImpl.Bar)); var iBarMethodInfo = typeof(IFoo).GetMethod(nameof(IFoo.Bar)); var compiledToModuleDirect = CompileToModule<FooImpl>(); var compiledToModuleVirtual = CompileToModule<IFoo>(); var iterationCount = 200000000; Console.WriteLine($"Iteration count: {iterationCount:N0}"); var sw = Stopwatch.StartNew(); for (int i = 0; i < iterationCount; i++) compiledVirtualCall(); var elapsedMs = sw.ElapsedMilliseconds; Console.WriteLine($"Virtual (Func<int>)Expression.Compile(): {elapsedMs} ms"); sw.Restart(); for (int i = 0; i < iterationCount; i++) compiledDirectCall(); elapsedMs = sw.ElapsedMilliseconds; Console.WriteLine($"Direct (Func<int>)Expression.Compile(): {elapsedMs} ms"); sw.Restart(); for (int i = 0; i < iterationCount; i++) compiledArgVirtualCall(iFoo); elapsedMs = sw.ElapsedMilliseconds; Console.WriteLine($"Virtual (Func<IFoo, int>)Expression.Compile(): {elapsedMs} ms"); sw.Restart(); for (int i = 0; i < iterationCount; i++) compiledArgDirectCall(foo); elapsedMs = sw.ElapsedMilliseconds; Console.WriteLine($"Direct (Func<FooImpl, int>)Expression.Compile(): {elapsedMs} ms"); sw.Restart(); for (int i = 0; i < iterationCount; i++) compiledToModuleVirtual(iFoo); elapsedMs = sw.ElapsedMilliseconds; Console.WriteLine($"Virtual (Func<IFoo, int>)Expression.CompileToMethod(): {elapsedMs} ms"); sw.Restart(); for (int i = 0; i < iterationCount; i++) compiledToModuleDirect(foo); elapsedMs = sw.ElapsedMilliseconds; Console.WriteLine($"Direct (Func<FooImpl, int>)Expression.CompileToMethod(): {elapsedMs} ms"); sw.Restart(); for (int i = 0; i < iterationCount; i++) virtualLambda(); elapsedMs = sw.ElapsedMilliseconds; Console.WriteLine($"Virtual () => IFoo.Bar(): {elapsedMs} ms"); sw.Restart(); for (int i = 0; i < iterationCount; i++) directLambda(); elapsedMs = sw.ElapsedMilliseconds; Console.WriteLine($"Direct () => FooImpl.Bar(): {elapsedMs} ms"); sw.Restart(); for (int i = 0; i < iterationCount; i++) iFoo.Bar(); elapsedMs = sw.ElapsedMilliseconds; Console.WriteLine($"Virtual IFoo.Bar(): {elapsedMs} ms"); sw.Restart(); for (int i = 0; i < iterationCount; i++) foo.Bar(); elapsedMs = sw.ElapsedMilliseconds; Console.WriteLine($"Direct Foo.Bar(): {elapsedMs} ms"); sw.Restart(); for (int i = 0; i < iterationCount; i++) { int result = (int)iBarMethodInfo.Invoke(iFoo, null); } elapsedMs = sw.ElapsedMilliseconds; Console.WriteLine($"Virtual MethodInfo.Invoke(FooImpl, Bar): {elapsedMs} ms"); sw.Restart(); for (int i = 0; i < iterationCount; i++) { int result = (int)barMethodInfo.Invoke(foo, null); } elapsedMs = sw.ElapsedMilliseconds; Console.WriteLine($"Direct MethodInfo.Invoke(IFoo, Bar): {elapsedMs} ms"); } static Func<int> CompileBar(IFoo foo, bool asInterfaceCall) { var fooType = asInterfaceCall ? typeof(IFoo) : foo.GetType(); var methodInfo = fooType.GetMethod(nameof(IFoo.Bar)); var instance = Expression.Constant(foo, fooType); var call = Expression.Call(instance, methodInfo); var lambda = Expression.Lambda(call); var compiledFunction = (Func<int>)lambda.Compile(); return compiledFunction; } static Func<TInput, int> CompileBar<TInput>() { var fooType = typeof(TInput); var methodInfo = fooType.GetMethod(nameof(IFoo.Bar)); var instance = Expression.Parameter(fooType, "foo"); var call = Expression.Call(instance, methodInfo); var lambda = Expression.Lambda(call, instance); var compiledFunction = (Func<TInput, int>)lambda.Compile(); return compiledFunction; } static Func<TInput, int> CompileToModule<TInput>() { var fooType = typeof(TInput); var methodInfo = fooType.GetMethod(nameof(IFoo.Bar)); var instance = Expression.Parameter(fooType, "foo"); var call = Expression.Call(instance, methodInfo); var lambda = Expression.Lambda(call, instance); var asmName = new AssemblyName(fooType.Name); var asmBuilder = AssemblyBuilder.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.Run); var moduleBuilder = asmBuilder.DefineDynamicModule(fooType.Name); var typeBuilder = moduleBuilder.DefineType(fooType.Name, TypeAttributes.Public); var methodBuilder = typeBuilder.DefineMethod(nameof(IFoo.Bar), MethodAttributes.Static, typeof(int), new[] { fooType }); Expression.Lambda<Action>(lambda).CompileToMethod(methodBuilder); var createdType = typeBuilder.CreateType(); var mi = createdType.GetMethods(BindingFlags.NonPublic | BindingFlags.Static)[1]; var func = Delegate.CreateDelegate(typeof(Func<TInput, int>), mi); return (Func<TInput, int>)func; } } }
On my laptop (Release mode, 64 bit, .NET 4.5.2) it yields:
Iteration count: 200,000,000 Virtual MethodInfo.Invoke(FooImpl, Bar): 61811 ms Direct MethodInfo.Invoke(IFoo, Bar): 37078 ms Virtual (Func<int>)Expression.Compile(): 2894 ms Direct (Func<int>)Expression.Compile(): 2242 ms Virtual (Func<IFoo, int>)Expression.Compile(): 2319 ms Direct (Func<FooImpl, int>)Expression.Compile(): 2051 ms Virtual (Func<IFoo, int>)Expression.CompileToMethod(): 996 ms Direct (Func<FooImpl, int>)Expression.CompileToMethod(): 679 ms Virtual () => IFoo.Bar(): 796 ms Direct () => FooImpl.Bar(): 469 ms Virtual IFoo.Bar(): 531 ms Direct Foo.Bar(): 68 ms
Hope this helps.
We can split a question to 2 cases:
ExpressionTest.exe in Release mode with optimization (default release settings) .NET 4.5.2:
Compiled Virtual Call: 4625 ms Compiled Direct Call: 3361 ms Lambda Virtual Call: 1096 ms Lambda Direct Call: 576 ms Virtual Call: 649 ms Direct Call: 144 ms
We see that "Direct Call" in 4.5 times faster than "Virtual Call". But as we see above it's no call at all. Bar method was inlined.
ExpressionTest.exe in Release mode with no optimization .NET 4.5.2:
Compiled Virtual Call: 5394 ms Compiled Direct Call: 4666 ms Lambda Virtual Call: 1800 ms Lambda Direct Call: 1683 ms Virtual Call: 1154 ms Direct Call: 1112 ms
So, "Direct Call" is about 3-4% faster than "Virtual Call".
Similar question: Performance of "direct" virtual call vs. interface call in C#
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With