2011-12-31 17 views
6

Dato seguente codice:metodo ILGenerator inlining

using System; 
using System.Reflection.Emit; 
using System.Diagnostics; 
using System.Reflection; 

namespace ConsoleApplication1 
{ 
    class A 
    { 
     public int Do(int n) 
     { 
      return n; 
     } 
    } 

    public delegate int DoDelegate(); 

    class Program 
    { 
     public static void Main(string[] args) 
     { 
      A a = new A(); 

      Stopwatch stopwatch = Stopwatch.StartNew(); 
      int s = 0; 
      for (int i = 0; i < 100000000; i++) 
      { 
       s += a.Do(i); 
      } 

      Console.WriteLine(stopwatch.ElapsedMilliseconds); 
      Console.WriteLine(s); 


      DynamicMethod dm = new DynamicMethod("Echo", typeof(int), new Type[] { typeof(int) }, true); 
      ILGenerator il = dm.GetILGenerator(); 

      il.Emit(OpCodes.Ldarg_0); 
      il.Emit(OpCodes.Ret); 

      DynamicMethod dm2 = new DynamicMethod("Test", typeof(int), new Type[0]); 
      il = dm2.GetILGenerator(); 


      Label loopStart = il.DefineLabel(); 
      Label loopCond = il.DefineLabel(); 

      il.DeclareLocal(typeof(int)); // i 
      il.DeclareLocal(typeof(int)); // s 

      // s = 0; 
      il.Emit(OpCodes.Ldc_I4_0); 
      il.Emit(OpCodes.Stloc_1); 

      // i = 0; 
      il.Emit(OpCodes.Ldc_I4_0); 
      il.Emit(OpCodes.Stloc_0); 

      il.Emit(OpCodes.Br_S, loopCond); 

      il.MarkLabel(loopStart); 

      // s += Echo(i); 
      il.Emit(OpCodes.Ldloc_1); // Load s 
      il.Emit(OpCodes.Ldloc_0); // Load i 
      il.Emit(OpCodes.Call, dm); // Call echo method 
      il.Emit(OpCodes.Add); 
      il.Emit(OpCodes.Stloc_1); 

      // i++ 
      il.Emit(OpCodes.Ldloc_0); 
      il.Emit(OpCodes.Ldc_I4_1); 
      il.Emit(OpCodes.Add); 
      il.Emit(OpCodes.Stloc_0); 

      il.MarkLabel(loopCond); 

      // Check for loop condition 
      il.Emit(OpCodes.Ldloc_0); 
      il.Emit(OpCodes.Ldc_I4, 100000000); 
      il.Emit(OpCodes.Blt_S, loopStart); 

      il.Emit(OpCodes.Ldloc_1); 
      il.Emit(OpCodes.Ret); 


      DoDelegate doDel = (DoDelegate)dm2.CreateDelegate(typeof(DoDelegate)); 
      s = doDel.Invoke();  // Dummy run to force JIT 


      stopwatch = Stopwatch.StartNew(); 
      s = doDel.Invoke(); 
      Console.WriteLine(stopwatch.ElapsedMilliseconds); 
      Console.WriteLine(s); 
     } 
    } 
} 

Chiama per il metodo Do viene inline. Il ciclo termina in circa 40 ms. Se io, ad esempio, crei Do per essere una funzione virtuale, essa non viene evidenziata e il ciclo termina in 240 ms. Fin qui tutto bene. Quando utilizzo ILGenerator per generare il metodo Do (Echo) e quindi generare DynamicMethod con lo stesso loop del metodo principale specificato, la chiamata al metodo Echo non viene mai eseguita in linea e per il ciclo sono necessari circa 240 ms. Il codice MSIL è corretto poiché restituisce lo stesso risultato del codice C#. Ero sicuro che l'inlining del metodo è qualcosa che viene fatto dalla JIT, quindi non vedo alcun motivo per non incorporare il metodo Echo.

Qualcuno sa perché questo semplice metodo non verrà sottolineato dal JIT.

+0

Stai anche generando il codice che chiama il metodo Do() generato dinamicamente, o è quel codice noto in fase di compilazione? –

+0

È possibile includere l'esempio di codice completo che utilizza ILGenerator? E, solo per essere sicuri: stai testando sotto release build ** senza ** il debugger allegato? –

+0

Ho riadattato il post, fornendo il codice completo per l'app di test. Uso la versione di rilascio ed eseguo senza il debugger. C# per loop inline la chiamata al metodo ed è molto più veloce dell'IL per loop. – user102808

risposta

0

Se sto capendo correttamente, la mia ipotesi è che, poiché il metodo è generato dinamicamente, il compilatore JIT non sa di inserirlo per i chiamanti.

Ho scritto una grande quantità di IL ma non ho esaminato il comportamento inlining (principalmente perché i metodi dinamici sono in genere abbastanza veloci per i miei scopi senza ulteriore ottimizzazione).

Vorrei dare il benvenuto a qualcuno più esperto sull'argomento per dare un feedback (per favore non solo a bassa quota, mi piacerebbe imparare qualcosa qui se sbaglio).

non dinamici

  • si scrive "normale" codice .NET (ad esempio, C#, VB.NET, qualsiasi lingua CLS-aware)
  • IL si crea al momento della compilazione
  • il codice macchina viene creato in fase di runtime; metodi sono inline se del caso

dinamica

  • si scrive "normale" codice .NET il cui scopo è quello di creare un metodo dinamico
  • IL si crea al momento della compilazione per questo codice, ma il metodo dinamico non viene creato
  • il codice macchina viene creato in fase di esecuzione per il codice che genera il metodo dinamico
  • quando viene richiamato quel codice, la dinamica viene soddisfatta hod viene creato come IL in un complesso speciale
  • IL del metodo dinamico viene compilato in codice macchina
  • tuttavia, JIT compilatore non ricompila altri chiamanti all'inline il nuovo metodo dinamico. O forse gli altri chiamanti sono dinamici e non sono ancora stati creati.
+0

Ho aggiunto il codice di esempio fulle che riproduce il problema. Come puoi vedere, genero due metodi dinamici. Uno è estremamente semplice e restituisce solo lo stesso valore ottenuto come parametro. L'altro metodo esegue un ciclo for semplice (lo stesso del codice C# all'inizio). Non mi aspetto che JIT inline la chiamata al secondo metodo perché viene chiamato tramite un delegato. Howerer, mi aspetto che inline il primo metodo, quando viene chiamato all'interno del secondo metodo. Quando lo faccio da solo, ottengo un'esecuzione 10 volte più veloce dello stesso ciclo. – user102808

1

Dopo ulteriori indagini ho concluso seguente: metodi generati

  1. ILGenerator potrà mai ottenere inline. Non importa se li chiami usando delegato, da un altro DynamicMethod o da un metodo creato con MethodBuilder.
  2. I metodi esistenti (quelli codificati in C# e compilati da VS) possono essere inseriti solo se richiamati da un metodo creato con MethodBuilder.Se chiamati da DynamicMethod, non verranno mai evidenziati.

Ho concluso questo dopo aver testato a fondo molti campioni e esaminando il codice assembler finale.

+0

Fino a quando qualcuno non mostra in modo diverso, penso che questa sia una conclusione accurata. Non in linea I metodi dinamici avrebbero senso a volte (che è quello che ho suggerito nella mia risposta). Forse i progettisti del compilatore hanno deciso che sarebbe stato più semplice trattarlo come una regola per tutti i casi. Mi chiedo se gli alberi di espressione si comportino allo stesso modo: http://msdn.microsoft.com/en-us/library/bb397951.aspx –

+0

Sarebbe interessante controllare, anche se non ho avuto tempo (o bisogno) di giocare con alberi di espressione. – user102808

+0

Sicuro che questo include anche il codice IL generato e salvato come dll e caricato dopo aver salvato? Perché sono sicuro che non include gli assembly generati che sono stati salvati. Ad ogni modo, se la tua risposta è corretta, potresti semplicemente contrassegnarla come corretta. –

0

Si potrebbe provare a generare un assembly dinamico. La mia comprensione è che la maggior parte del runtime non è consapevole del fatto che sia dinamico. Penso che internamente venga caricato come un qualsiasi altro byte [] assembly. Pertanto il JIT/inliner potrebbe anche non esserne consapevole (o non gliene importa).

+0

L'ho provato anche io. In qualche modo e per qualche ragione, il metodo generato dinamicamente (non importa come lo generate) non si inline. Grazie comunque. – user102808

+0

Buono a sapersi. . – usr