2012-05-16 55 views
8

He leído varias publicaciones sobre SO sobre cómo escribir y compilar código dinámico de C#. Por ejemplo, this post. Entiendo que se puede hacer de varias maneras.Ejecuta el código C# compilado dinámicamente a la velocidad nativa ... ¿cómo?

Sin embargo, llamar al código invocador es lento. Hice un punto de referencia simple, y es unas 500 veces más lento que llamar a un método nativo.

Lo que quiero poder hacer es el equivalente a cargar un archivo DLL y llamar a uno de sus métodos directamente ("nativamente"), lo que dará los beneficios de velocidad que deseo.

¿Cuál es la forma más fácil de hacerlo? ¿Compila el código dinámico a un dll y luego lo carga? ¿Se puede hacer en la memoria?

EDITAR

no me importa el tiempo de compilación. Solo ejecución.

EDIT 2, 3

Aquí está el código de referencia que escribí:

public static int Execute(int i) { return i * 2; } 

    private void button30_Click(object sender, EventArgs e) 
    { 
     CSharpCodeProvider foo = new CSharpCodeProvider(); 

     var res = foo.CompileAssemblyFromSource(
      new System.CodeDom.Compiler.CompilerParameters() 
      { 
       GenerateInMemory = true, 
       CompilerOptions = @"/optimize",      
      }, 
      @"public class FooClass { public static int Execute(int i) { return i * 2; }}" 
     ); 

     var type = res.CompiledAssembly.GetType("FooClass"); 
     var obj = Activator.CreateInstance(type); 
     var method = type.GetMethod("Execute"); 
     int i = 0, t1 = Environment.TickCount, t2; 
     //var input = new object[] { 2 }; 

     //for (int j = 0; j < 10000000; j++) 
     //{ 
     // input[0] = j; 
     // var output = method.Invoke(obj, input); 
     // i = (int)output; 
     //} 

     //t2 = Environment.TickCount; 

     //MessageBox.Show((t2 - t1).ToString() + Environment.NewLine + i.ToString()); 

     t1 = Environment.TickCount; 

     for (int j = 0; j < 100000000; j++) 
     { 
      i = Execute(j); 
     } 

     t2 = Environment.TickCount; 

     MessageBox.Show("Native: " + (t2 - t1).ToString() + Environment.NewLine + i.ToString()); 

     var func = (Func<int, int>) Delegate.CreateDelegate(typeof (Func<int, int>), method); 

     t1 = Environment.TickCount; 

     for (int j = 0; j < 100000000; j++) 
     { 
      i = func(j); 
     } 

     t2 = Environment.TickCount; 

     MessageBox.Show("Dynamic delegate: " + (t2 - t1).ToString() + Environment.NewLine + i.ToString()); 

     Func<int, int> funcL = Execute; 

     t1 = Environment.TickCount; 

     for (int j = 0; j < 100000000; j++) 
     { 
      i = funcL(j); 
     } 

     t2 = Environment.TickCount; 

     MessageBox.Show("Delegate: " + (t2 - t1).ToString() + Environment.NewLine + i.ToString()); 
    } 
+0

¿Has tenido en cuenta el tiempo de compilación? ¿cómo se ve su punto de referencia? – Botz3000

+1

ejecutando C# build tiempo de ejecución no es 500 veces más lento que ejecutar C# build anterior. Son idénticos, sin embargo, hay gastos generales a considerar. ¿Cuál es su punto de referencia, está utilizando Reflection.Emit o algún servicio de compilación para compilar? –

+0

La invocación es lo lento, que es el centro de mi pregunta: ¿cómo se puede llamar al método a velocidad nativa? – IamIC

Respuesta

7

Sí, si se invoca a través de un MethodInfo o no específica Delegate, entonces será realmente lento . El truco es: no hagas eso. Diversos enfoques:

  • de métodos individuales, van a través de una básica pero mecanografiadas delegado, como Action, o como un cajón de sastre genérico, Func<object[], object> - y utilizar Delegate.CreateDelegate para crear un mecanografiadas delegado:

    Action doSomething = (Action)Delegate.CreateDelegate(typeof(Action), method); 
    

    otra variante de esta es el uso de la Expression API (que tiene un método .Compile()), o DynamicMethod (que tiene CreateDelegate()). La clave: debe obtener un delegado mecanografiado e invocar usando invocado invocar (no .DynamicInvoke).

  • para los casos más complejos en los que están generando los tipos enteros, considerar la implementación de una interfaz a conocer, es decir

    IFoo foo = (IFoo)Activator.CreateInstance(...); 
    

    otra vez; después del lanzamiento inicial (que es muy barato) sólo puede utilizar código estático:

    foo.Bar(); 
    

Haz no uso someDelegate.DynamicInvoke(...) o someMethod.Invoke(...) si después de cualquier tipo de actuación.

+0

Estoy totalmente de acuerdo con su conclusión, pero ¿no cree que una diferencia de magnitud de 500 está relacionada con algo más que la invocación dinámica (por ejemplo, errores de evaluación comparativa) –

+1

@RuneFS el método que se prueba es 'return i * 2;' - I piense que es perfectamente razonable esperar una magnitud de 500, es decir, el código que se prueba es extremadamente pequeño, y todo lo que se prueba es la velocidad de invocación. –

+0

@RuneFS Para obtener más información, consulte aquí: http://msmvps.com/blogs/jon_skeet/archive/2008/08/09/making-reflection-fly-and-exploring-delegates.aspx – sloth

3

Además de los consejos de Marc se podría mejorar la velocidad mediante la especificación de la opción de compilador "optimizar":

var res = foo.CompileAssemblyFromSource(
     new System.CodeDom.Compiler.CompilerParameters() 
     { 
      GenerateInMemory = true, 
      CompilerOptions = "/optimize" 
     }, 
1

pensó que era digno de mostrar cómo todas las opciones posibles se veía y sus características de rendimiento. Dadas las siguientes clases de ayuda y funciones:

public void Test(Func<int> func) 
{   
    var watch = new Stopwatch(); 
    watch.Start(); 
    for (var i = 0; i <= 1000000; i++) 
    { 
     var test = func(); 
    } 
    Console.WriteLine(watch.ElapsedMilliseconds); 
} 

public class FooClass { public int Execute() { return 1;}} 

configurar y ejecución:

using (Microsoft.CSharp.CSharpCodeProvider foo = 
     new Microsoft.CSharp.CSharpCodeProvider()) 
{ 
    var res = foo.CompileAssemblyFromSource(
     new System.CodeDom.Compiler.CompilerParameters() 
     { 
      GenerateInMemory = true 
     }, 
     "public class FooClass { public int Execute() { return 1;}}" 
    ); 

    var real = new FooClass(); 
    Test(() => real.Execute());     // benchmark, direct call 

    var type = res.CompiledAssembly.GetType("FooClass"); 
    var obj = Activator.CreateInstance(type);  
    var method = type.GetMethod("Execute"); 
    var input = new object[] { };     
    Test(() => (int)method.Invoke(obj, input)); // reflection invoke 

    dynamic dyn = Activator.CreateInstance(type); 
    Test(() => dyn.Execute());     // dynamic object invoke 

    var action = (Func<int>)Delegate.CreateDelegate(typeof(Func<int>), null, method); 
    Test(() => action());       // delegate 
} 

Los resultados son los siguientes:

8  // direct 
771 // reflection invoke 
41 // dynamic object invoke 
7  // delegate 

Así que en aquellos casos donde no se puede utilizar delegados (si no sabe lo suficiente?), puede intentar dynamic.

+0

Tengo diferentes tiempos en mi prueba. Para el mío, directo fue 7.8, el delegado fue 43.7. – IamIC

Cuestiones relacionadas