2016-03-04 26 views
12

我很好奇Expression.Compile與代碼和直接方法使用中的lambda表達式的對比,以及直接方法調用與虛擬方法調用(僞代碼):Expression.Compile vs 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); 
+0

你說的 「有多好」 呢?你在尋找執行表現嗎? –

+0

謝謝,澄清 –

回答

21

我沒有找到任何答案,所以這裏是性能測試:

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

在我的筆記本電腦(Release模式下,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<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 

希望這會有所幫助。

3

提示:在釋放模式下,在「直接呼叫」情況下完全沒有呼叫。 僅從00B531BC(mov eax ...)到00B531C8(jl 00B531BC)的CPU。

   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 
+1

這是正確的,它確實內聯,謝謝。這應該是一個意見 –

6

我們可以將問題分成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%。

類似的問題: Performance of "direct" virtual call vs. interface call in C#

+0

我更感興趣的是爲什麼Expression.Compile()提供了一個較慢版本的方法與lambda「()=> {...}」,是否有一種方法來獲得優化代碼出Expression.Compile()? –

+0

需要使用CompileToMethod弄清楚.. –

+0

我也很感興趣,爲什麼編譯的調用(包括Method和Delegate)比從lambda表達式中快速構造新的表達式樹,然後執行樹... 。我也會對「CompileToModule」比「CompileBar」運行多長時間以及內存使用情況感興趣。 –