Pregunta sobre assemblies, c#, compiler-construction, dynamic – Ejecute el código C # compilado dinámicamente a velocidad nativa ... ¿cómo?

9

He leído varias publicaciones en SO sobre cómo escribir y compilar código dinámico de C #. Por ejemplo,esta publicación. Entiendo que se puede hacer de varias maneras.

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

Lo que quiero poder hacer es el equivalente a cargar una DLL y llamar a uno de sus métodos directamente ("de forma nativa"), lo que proporcionará los beneficios de velocidad que deseo.

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

EDITAR

No me importa el tiempo de compilación. Sólo ejecución.

Editar 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());
    }
¿Has tenido en cuenta el tiempo de compilación? ¿Cómo se ve tu punto de referencia? Botz3000
Como nota final, la prueba anterior, cuando se cronometra con mayor precisión, es 1100 X más lenta. La solución delegada que Marc dio es 8.4 X más lenta que la nativa. IamIC
ejecutar C # build runtime no es 500 veces más lento que ejecutar C # build antes. Son idénticos, sin embargo hay gastos generales que deben considerarse. ¿Cómo está su punto de referencia, está utilizando Reflection.Emit o algún servicio de compilación para compilar? Rune FS
La invocación es lo que es lento, que es el centro de mi pregunta: ¿cómo se puede llamar al método a velocidad nativa? IamIC

Tu respuesta

3   la respuesta
1

ciales y sus características de rendimiento. Dadas las siguientes clases y funciones de ayuda:

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;}}

Configuración 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:

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

Entonces, en aquellos casos en los que no puede utilizar delegados (si no sabe lo suficiente), puede intentardynamic.

Tengo diferentes horarios en mi prueba. Para el mío, directo fue de 7.8, delegado fue de 43.7. IamIC
8

MethodInfo o un no específicoDelegate, entonces de hecho será lento. El truco es:no hagas eso. Varios enfoques:

Para los métodos individuales, vaya a través de un básicopero mecanografiado delegado, comoAction, o como un genérico catch-all,Func<object[], object> - y useDelegate.CreateDelegate para crear unmecanografiado delegar:

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

Otra variante de esto es usar elExpression API (que tiene un.Compile() método), oDynamicMethod (que tieneCreateDelegate()). La clave: debes conseguir unmecanografiado delegar e invocar usandomecanografiado invocar.DynamicInvoke).

para casos más complejos en los que está generando tipos enteros, considere implementar una interfaz que conozca, es decir,

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

otra vez; Después del lanzamiento inicial (que es muy barato), puedes usar código estático:

foo.Bar();

Hacerno utilizarsomeDelegate.DynamicInvoke(...) osomeMethod.Invoke(...) Si buscas algún tipo de actuación.

@MarcGravell De hecho, publiqué esto como una pregunta separada. Algunos carteles informaron un factor de 4. No estoy seguro de por qué existe una gran diferencia entre los sistemas. IamIC
Supongo que no estaba claro entonces ese es mi punto exactamente, no es la velocidad de ejecución del código compilado que se está midiendo. Rune FS
@IanC ¿Supongo que está en lanzamiento, línea de comandos, optimizado, etc.? Estoy en un quad-core (8 con HT) i7. Marc Gravell
@MarcGravell Quiero tu computadora, entonces. Estamos ejecutando el mismo código. IamIC
3

podría mejorar la velocidad especificando la opción de compilación "optimizar":

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

Preguntas relacionadas