Pergunta sobre c#, dynamic, compiler-construction, assemblies – Execute o código C # dinamicamente compilado na velocidade nativa… como?

9

Eu li várias postagens sobre SO sobre como escrever e compilar código dinâmico em C #. Por exemplo,esta postagem. Eu entendo que isso pode ser feito de várias maneiras.

No entanto, chamar o invoker de código é lento. Eu fiz um benchmark simples, e são 500 X mais lentos do que chamar um método nativo.

O que eu quero ser capaz de fazer é o equivalente a carregar uma DLL e chamar diretamente um dos seus métodos ("nativamente"), o que dará a velocidade que eu quero.

Qual é a maneira mais fácil de fazer isso? Compilar o código dinâmico para uma dll e, em seguida, carregá-lo? Pode ser feito na memória?

EDITAR

Eu não me importo com o tempo de compilação. Apenas execução.

EDITAR 2, 3

Aqui está o código de benchmark que eu escrevi:

    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());
    }
Como nota final, o teste acima, quando mais precisamente cronometrado, é realmente 1100 X mais lento. A solução de delegação que Marc deu é 8.4 X mais lenta que a nativa. IamIC
Você já levou em conta o tempo de compilação? como está seu benchmark? Botz3000
Executar o tempo de execução de compilação do C # não é 500 vezes mais lento do que executar a compilação do C # anterior. Eles são idênticos, mas há despesas gerais a serem consideradas. Como é o seu benchmark, você está usando Reflection.Emit ou algum serviço de compilador para compilar? Rune FS
O Invoke é o que é lento, que é o centro da minha pergunta: como você pode chamar o método na velocidade nativa? IamIC

Sua resposta

3   a resposta
8

Sim, se você invocar por meio de umMethodInfo ou um não-específicoDelegateentão, de fato, será lento. O truque é:não faça isso. Várias abordagens:

para métodos individuais, vá através de ummas digitado delegado, comoAction, ou como um genérico catch-all,Func<object[], object> - E useDelegate.CreateDelegate para criar umdigitado delegar:

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

outra variante disso é usar oExpression API (que tem um.Compile() método), ouDynamicMethod (que temCreateDelegate()). A principal coisa: você deve obter umdigitado delegar e invocar usandodigitado invocar (não.DynamicInvoke).

Para casos mais complexos em que você está gerando tipos inteiros, considere a implementação de uma interface que você conhece, ou seja,

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

novamente; Após o elenco inicial (que é muito barato) você pode simplesmente usar o código estático:

foo.Bar();

Faznão usarsomeDelegate.DynamicInvoke(...) ousomeMethod.Invoke(...) se você está atrás de qualquer tipo de performance.

@MarcGravell Eu também usei um delegado que apontava para um método estático local (não compilado dinamicamente) e obtinha os mesmos resultados de velocidade. IamIC
Muito obrigado @MarcGravell :) Isso resolve ... Vou procurar essas soluções no MSDN. IamIC
@ IanC Eu suponho que você está no lançamento, linha de comando, otimizado, etc? Eu estou em um quad-core (8 com HT) i7. Marc Gravell
@MarcGravell por puro interesse, eu re-testado isso em um i7 e sob .net 4.5, e é um pouco mais de 1.4 X diferença agora. Mesmo código. IamIC
3

Além do conselho de Marc, você poderia melhorar a velocidade especificando a opção "otimizar" do compilador:

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

Pensei que valeria a pena mostrar como todas as opções possíveis pareciam e suas características de desempenho. Dadas as seguintes classes e funções auxiliares:

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 e executar:

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
}

Os resultados são:

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

Então, nos casos em que você não pode usar delegados (se você não sabe o suficiente?), Você pode tentardynamic.

Eu tenho tempos diferentes no meu teste. Para o meu, direto foi de 7,8, o delegado foi de 43,7. IamIC

Perguntas relacionadas