Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Performance of Expression.Compile vs Lambda, direct vs virtual calls

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); 
like image 916
Serge Semenov Avatar asked Mar 04 '16 20:03

Serge Semenov


2 Answers

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.

like image 101
Serge Semenov Avatar answered Oct 07 '22 08:10

Serge Semenov


We can split a question to 2 cases:

  • how bare .NET work with method calls itself (infrastructure question)?
  • how optimizers assist to method calling?

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#

like image 28
Dmitry Razumikhin Avatar answered Oct 07 '22 07:10

Dmitry Razumikhin