Attualmente sto facendo alcune ottimizzazioni dell'ultima misura, principalmente per divertimento e apprendimento, e ho scoperto qualcosa che mi ha lasciato un paio di domande.Curiosità: Perché Expression <...> durante la compilazione viene eseguito più rapidamente di un metodo dinamico minimo?
lato, le questioni:
- Quando costruiscono un metodo in memoria attraverso l'uso di DynamicMethod, e utilizzare il debugger, non v'è alcun modo per me di passare nel codice assembly generato, quando vieweing il codice nella vista del disassemblatore? Il debugger sembra semplicemente scavalcare l'intero metodo per me
- Oppure, se ciò non è possibile, è possibile che io in qualche modo salvi il codice IL generato su disco come un assieme, in modo che possa ispezionarlo con Reflector?
- Perché la versione
Expression<...>
del mio metodo di aggiunta semplice (Int32 + Int32 => Int32) è più veloce di una versione minima di DynamicMethod?
Ecco un breve e completo programma che dimostra. Sul mio sistema, l'output è:
DynamicMethod: 887 ms
Lambda: 1878 ms
Method: 1969 ms
Expression: 681 ms
Mi aspettavo la lambda e chiamate di metodo ad avere valori più alti, ma la versione DynamicMethod è costantemente circa 30-50% più lento (variazioni probabilmente a causa di Windows e altri programmi). Qualcuno conosce la ragione?
Ecco il programma:
using System;
using System.Linq.Expressions;
using System.Reflection.Emit;
using System.Diagnostics;
namespace Sandbox
{
public class Program
{
public static void Main(String[] args)
{
DynamicMethod method = new DynamicMethod("TestMethod",
typeof(Int32), new Type[] { typeof(Int32), typeof(Int32) });
var il = method.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Add);
il.Emit(OpCodes.Ret);
Func<Int32, Int32, Int32> f1 =
(Func<Int32, Int32, Int32>)method.CreateDelegate(
typeof(Func<Int32, Int32, Int32>));
Func<Int32, Int32, Int32> f2 = (Int32 a, Int32 b) => a + b;
Func<Int32, Int32, Int32> f3 = Sum;
Expression<Func<Int32, Int32, Int32>> f4x = (a, b) => a + b;
Func<Int32, Int32, Int32> f4 = f4x.Compile();
for (Int32 pass = 1; pass <= 2; pass++)
{
// Pass 1 just runs all the code without writing out anything
// to avoid JIT overhead influencing the results
Time(f1, "DynamicMethod", pass);
Time(f2, "Lambda", pass);
Time(f3, "Method", pass);
Time(f4, "Expression", pass);
}
}
private static void Time(Func<Int32, Int32, Int32> fn,
String name, Int32 pass)
{
Stopwatch sw = new Stopwatch();
sw.Start();
for (Int32 index = 0; index <= 100000000; index++)
{
Int32 result = fn(index, 1);
}
sw.Stop();
if (pass == 2)
Debug.WriteLine(name + ": " + sw.ElapsedMilliseconds + " ms");
}
private static Int32 Sum(Int32 a, Int32 b)
{
return a + b;
}
}
}
Interessante domanda. Questo tipo di cose può essere risolto usando WinDebug e SOS. Ho pubblicato un passo alla volta di un'analoga analisi che ho fatto molte lune fa nel mio blog, http://blog.barrkel.com/2006/05/clr-tailcall-optimization-or-lack.html –
Ho pensato che avrei dovuto eseguire il ping tu - ho scoperto come forzare JIT senza dover chiamare il metodo una volta. Utilizzare l'argomento del costruttore DynamicMethod 'restrictedSkipVisibility'. A seconda del contesto (sicurezza del codice), potrebbe non essere disponibile. –
Davvero una buona domanda. Innanzitutto, per questo tipo di profilazione, userei una versione/Console - quindi il 'Debug.WriteLine' sembra fuori luogo; ma anche con 'Console.WriteLine' le mie statistiche sono simili: DynamicMethod: 630 ms Lambda: 561 ms Metodo: 553 ms Espressione: 360 ms Sto ancora cercando ... –