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.
Stai anche generando il codice che chiama il metodo Do() generato dinamicamente, o è quel codice noto in fase di compilazione? –
È possibile includere l'esempio di codice completo che utilizza ILGenerator? E, solo per essere sicuri: stai testando sotto release build ** senza ** il debugger allegato? –
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