Actualmente estoy haciendo algunas optimizaciones de última medida, principalmente por diversión y aprendizaje, y descubrí algo que me dejó un par de preguntas.Curiosidad: ¿Por qué la expresión <...>, cuando se compila, se ejecuta más rápido que un mínimo DynamicMethod?
En primer lugar, las preguntas:
- Cuando construyo un método en memoria mediante el uso de DynamicMethod, y utilizar el depurador, ¿hay alguna manera para mí para entrar en el código ensamblador generado, cuando vieweing el código en la vista del desensamblador? Parece que el depurador simplemente pasa por encima del método completo
- O, si eso no es posible, ¿es posible para mí guardar de alguna manera el código IL generado en el disco como un ensamblaje, para poder inspeccionarlo con Reflector?
- ¿Por qué la versión
Expression<...>
de mi método de adición simple (Int32 + Int32 => Int32) se ejecuta más rápido que una versión mínima de DynamicMethod?
Aquí hay un programa breve y completo que demuestra. En mi sistema, la salida es:
DynamicMethod: 887 ms
Lambda: 1878 ms
Method: 1969 ms
Expression: 681 ms
lo esperado El método de lambda y llama a tener valores más altos, pero la versión DynamicMethod es consistentemente alrededor de 30-50% más lentas (variaciones probablemente debido a Windows y otros programas). Alguien sabe el motivo?
Aquí está el programa:
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;
}
}
}
Interesante pregunta. Este tipo de cosas se pueden resolver usando WinDebug y SOS. Publiqué un paso a paso de un análisis similar que hice hace muchas lunas en mi blog, http://blog.barrkel.com/2006/05/clr-tailcall-optimization-or-lack.html –
. Pensé que debería hacer ping. usted - descubrí cómo forzar JIT sin tener que llamar al método una vez. Use el argumento del constructor 'DynamicSkipVisibility' DynamicMethod. Dependiendo del contexto (seguridad del código), puede que no esté disponible. –
Muy buena pregunta. Primero, para este tipo de perfiles, utilizaría una versión/consola, por lo que 'Debug.WriteLine' parece fuera de lugar; pero incluso con 'Console.WriteLine' mis estadísticas son similares: DynamicMethod: 630 ms Lambda: 561 ms Método: 553 ms Expresión: 360 ms Todavía estoy buscando ... –