2016-03-04 37 views
12

Sono curioso di sapere quanto performante sia l'espressione Expression.Compile rispetto a lambda nel codice e l'utilizzo diretto del metodo e anche le chiamate di metodo dirette rispetto alle chiamate di metodo virtuali (pseudo codice):Prestazioni di Expression.Compile vs Lambda, chiamate dirette e virtuali

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

Che cosa si intende per "quanto è buono"? Stai cercando prestazioni di esecuzione? –

+0

Grazie, chiarito –

risposta

21

non ho trovato alcuna risposta, ecco la prova di prestazione:

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

Sul mio portatile (modalità di rilascio, a 64 bit, NET 4.5.2) se ne ricava:

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 

Spero che questo aiuti.

3

Suggerimento: in modalità di rilascio nessuna chiamata effettuata nel caso "Chiamata diretta". CPU che va da 00B531BC (mov eax ...) a 00B531C8 (jl 00B531BC) solo.

   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

Esatto, fa inlining, grazie. Questo dovrebbe essere un commento pensato –

6

Siamo in grado di dividere una domanda a 2 casi:

  • come nuda lavoro .NET con metodo stesso (domanda di infrastrutture) chiama?
  • in che modo gli ottimizzatori supportano il metodo di chiamata?

ExpressionTest.exe in uscita modalità con (impostazioni di rilascio di default) l'ottimizzazione .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 

vediamo che "Direct Call" a 4,5 volte più veloce di "Virtual Chiamata". Ma come vediamo sopra non c'è nessuna chiamata. Il metodo bar era in linea.

ExpressionTest.exe in uscita modalità senza ottimizzazione .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 

Quindi, "Direct Call" è di circa il 3-4% più veloce di "Call virtuale".

domanda simile: Performance of "direct" virtual call vs. interface call in C#

+0

Sono più interessato al motivo per cui Expression.Compile() fornisce una versione più lenta del metodo rispetto a lambda "() => {...}" ed esiste un modo per ottenere un ottimizzato codice da Expression.Compile()? –

+0

È necessario utilizzare CompileToMethod per capire ... –

+0

Mi interessa anche il motivo per cui le chiamate compilate (sia a Method che a Delegate) sono più lente della costruzione di un nuovo albero di espressioni dall'espressione lambda al volo e quindi dell'esecuzione dell'albero ... Sarei anche interessato a quanto tempo "CompileToModule" ha impiegato per eseguire piuttosto che "CompileBar" e quale fosse l'utilizzo della memoria. –