2016-03-04 12 views
12

Jestem ciekawy, jak wydajność Expression.Compile jest w porównaniu do wyrażenia lambda w kodzie i w porównaniu do użycia metody bezpośredniej, a także bezpośrednie wywołania metod vs wywołania metod wirtualnych (pseudo kod):Wydajność Expression.Compile vs Lambda, bezpośrednie vs wirtualnych połączeń

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

Co masz na myśli przez "jak dobrze"? Szukasz wydajności wykonania? –

+0

Dzięki, wyjaśniłem –

Odpowiedz

21

nie mogę znaleźć żadnej odpowiedzi, więc tutaj jest test wydajności:

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

na moim laptopie (w trybie Release, 64 bit, .NET 4.5.2) to daje:

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 

Mam nadzieję, że to pomoże.

3

Wskazówka: w trybie zwolnienia nie było żadnego połączenia w przypadku "Połączenie bezpośrednie". Procesor od 00B531BC (mov eax ...) do 00B531C8 (jl 00B531BC) tylko.

   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

Zgadza się, robi to, dziękuję. To powinno być komentarzem myśl –

6

Możemy podzielić pytanie 2 przypadkach:

  • jak nagi praca .NET metodą nazywa się (pytanie infrastruktura)?
  • w jaki sposób optymalizatory pomagają w nawiązywaniu połączeń?

ExpressionTest.exe w Release tryb z (domyślne ustawienia release) optymalizacja 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 

widzimy, że "bezpośrednie połączenie" w 4,5 razy szybciej niż „Wirtualny Połączenie". Ale jak widzimy powyżej, nie ma w ogóle żadnego wezwania. Wprowadzono metodę prętową.

ExpressionTest.exe w Release tryb bez optymalizacji .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 

Tak, "Direct Call" jest około 3-4% szybciej niż "Virtual Call".

Podobne pytanie: Performance of "direct" virtual call vs. interface call in C#

+0

Jestem bardziej zainteresowany, dlaczego Expression.Compile() daje wolniejszą wersję metody vs. lambda "() => {...}" i czy istnieje sposób, aby uzyskać zoptymalizowane kod z Expression.Compile()? –

+0

Trzeba użyć CompileToMethod, aby dowiedzieć się .. –

+0

Jestem również zainteresowany, dlaczego skompilowane wywołania (zarówno do metody i delegowania) są wolniejsze niż konstruowanie nowego drzewa wyrażeń z wyrażenia lambda w locie, a następnie wykonanie drzewa ... Byłbym również zainteresowany tym, jak długo "CompileToModule" zaczął działać, niż "CompileBar" i jakie było użycie pamięci. –

Powiązane problemy