Expression.Compile与Lambda的性能,直接与虚拟调用

我很好奇Expression.Compile与代码中的lambda表达式和直接方法使用方式的性能如何,以及直接方法调用与虚方法调用(伪代码):

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); 

我没有找到任何答案,所以这是性能测试:

 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 directLambda = () => foo.Bar(); Func virtualLambda = () => iFoo.Bar(); var compiledDirectCall = CompileBar(foo, asInterfaceCall: false); var compiledVirtualCall = CompileBar(foo, asInterfaceCall: true); var compiledArgDirectCall = CompileBar(); var compiledArgVirtualCall = CompileBar(); var barMethodInfo = typeof(FooImpl).GetMethod(nameof(FooImpl.Bar)); var iBarMethodInfo = typeof(IFoo).GetMethod(nameof(IFoo.Bar)); var compiledToModuleDirect = CompileToModule(); var compiledToModuleVirtual = CompileToModule(); 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)Expression.Compile(): {elapsedMs} ms"); sw.Restart(); for (int i = 0; i < iterationCount; i++) compiledDirectCall(); elapsedMs = sw.ElapsedMilliseconds; Console.WriteLine($"Direct (Func)Expression.Compile(): {elapsedMs} ms"); sw.Restart(); for (int i = 0; i < iterationCount; i++) compiledArgVirtualCall(iFoo); elapsedMs = sw.ElapsedMilliseconds; Console.WriteLine($"Virtual (Func)Expression.Compile(): {elapsedMs} ms"); sw.Restart(); for (int i = 0; i < iterationCount; i++) compiledArgDirectCall(foo); elapsedMs = sw.ElapsedMilliseconds; Console.WriteLine($"Direct (Func)Expression.Compile(): {elapsedMs} ms"); sw.Restart(); for (int i = 0; i < iterationCount; i++) compiledToModuleVirtual(iFoo); elapsedMs = sw.ElapsedMilliseconds; Console.WriteLine($"Virtual (Func)Expression.CompileToMethod(): {elapsedMs} ms"); sw.Restart(); for (int i = 0; i < iterationCount; i++) compiledToModuleDirect(foo); elapsedMs = sw.ElapsedMilliseconds; Console.WriteLine($"Direct (Func)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 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)lambda.Compile(); return compiledFunction; } static Func CompileBar() { 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)lambda.Compile(); return compiledFunction; } static Func CompileToModule() { 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(lambda).CompileToMethod(methodBuilder); var createdType = typeBuilder.CreateType(); var mi = createdType.GetMethods(BindingFlags.NonPublic | BindingFlags.Static)[1]; var func = Delegate.CreateDelegate(typeof(Func), mi); return (Func)func; } } } 

在我的笔记本电脑上(发布模式,64位,.NET 4.5.2),它产生:

 Iteration count: 200,000,000 Virtual MethodInfo.Invoke(FooImpl, Bar): 61811 ms Direct MethodInfo.Invoke(IFoo, Bar): 37078 ms Virtual (Func)Expression.Compile(): 2894 ms Direct (Func)Expression.Compile(): 2242 ms Virtual (Func)Expression.Compile(): 2319 ms Direct (Func)Expression.Compile(): 2051 ms Virtual (Func)Expression.CompileToMethod(): 996 ms Direct (Func)Expression.CompileToMethod(): 679 ms Virtual () => IFoo.Bar(): 796 ms Direct () => FooImpl.Bar(): 469 ms Virtual IFoo.Bar(): 531 ms Direct Foo.Bar(): 68 ms 

希望这可以帮助。

我们可以将问题分为2个案例:

  • .NET如何使用方法调用自身(基础设施问题)?
  • 优化器如何协助方法调用?

具有优化的 发布模式下的ExpressionTest.exe(默认发行版设置).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 

我们看到“直接呼叫”比“虚拟呼叫”快4.5倍。 但正如我们在上面看到的那样根本没有电话。 Bar方法被内联。

ExpressionTest.exe处于发布模式,没有优化 .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 

因此,“直接呼叫”比“虚拟呼叫”快3-4%。

类似的问题: C#中“直接”虚拟呼叫与接口呼叫的性能

提示:在发布模式下,在“直接呼叫”情况下根本没有呼叫。 CPU仅从00B531BC( mov eax … )到00B531C8( jl 00B531BC )。

  for (int i = 0; i < iterationCount; i++) 00B531BA xor edx,edx foo.Bar(); 00B531BC mov eax,dword ptr [ebx+4] // actual loop begin 00B531BF cmp byte ptr [eax],al for (int i = 0; i < iterationCount; i++) 00B531C1 inc edx 00B531C2 cmp edx,0BEBC200h // 0BEBC200h = 200000000 00B531C8 jl 00B531BC // loop begin address