Frage an assemblies, dynamic, compiler-construction, c# – Dynamisch kompilierten C # -Code mit nativer Geschwindigkeit ausführen ... wie?

9

Ich habe mehrere Posts über das Schreiben und Kompilieren von dynamischem C # -Code auf SO gelesen. Zum Beispiel,dieser Beitrag. Ich verstehe, es kann auf verschiedene Arten geschehen.

Das Aufrufen des Codeaufrufers ist jedoch langsam. Ich habe ein einfaches Benchmarking durchgeführt und es ist ungefähr 500 Mal langsamer als das Aufrufen einer nativen Methode.

Ich möchte in der Lage sein, eine DLL zu laden und eine ihrer Methoden direkt ("nativ") aufzurufen, wodurch sich die gewünschten Geschwindigkeitsvorteile ergeben.

Was ist der einfachste Weg, dies zu tun? Den dynamischen Code in eine DLL kompilieren und dann laden? Kann es im Gedächtnis geschehen?

BEARBEITEN

Die Kompilierungszeit ist mir egal. Nur Hinrichtung.

EDIT 2, 3

Hier ist der Benchmark-Code, den ich geschrieben habe:

    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());
    }
Das Ausführen der C # -Erstellungslaufzeit ist nicht 500-mal langsamer als das Ausführen der vorherigen C # -Erstellung. Sie sind identisch, es sind jedoch zusätzliche Kosten zu berücksichtigen. Wie sieht Ihr Benchmark aus? Verwenden Sie Reflection.Emit oder einen Compiler-Dienst zum Kompilieren? Rune FS
Haben Sie die Kompilierungszeit berücksichtigt? Wie sieht Ihr Benchmark aus? Botz3000
Der Aufruf ist das, was langsam ist, was das Zentrum meiner Frage ist: Wie kann man die Methode mit nativer Geschwindigkeit aufrufen? IamIC
Als letzte Anmerkung ist der obige Test, wenn er genauer zeitlich abgestimmt ist, tatsächlich um das 1100-fache langsamer. Die Delegiertenlösung, die Marc gegeben hat, ist 8.4 X langsamer als die native. IamIC

Deine Antwort

3   die antwort
1

es lohnt sich zu zeigen, wie alle möglichen Optionen aussehen und welche Leistungsmerkmale sie haben. Ausgehend von folgenden Hilfsklassen und Funktionen:

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

Aufbau und Ausführung:

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
}

Die Ergebnisse sind:

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

In den Fällen, in denen Sie keine Stellvertreter einsetzen können (wenn Sie nicht genug wissen?), Können Sie es versuchendynamic.

Ich erhalte unterschiedliche Zeiten für meinen Test. Bei mir war direct 7.8, der Delegierte war 43.7. IamIC
8

MethodInfo oder eine unspezifischeDelegate, dann wird es ja langsam gehen. Der Trick ist:mach das nicht. Verschiedene Ansätze:

für einzelne methoden über ein basic gehenaber getippt delegieren, wie zAction, oder als generisches Allheilmittel,Func<object[], object> - und verwendenDelegate.CreateDelegate zu schaffen eingetippt delegieren:

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

Eine andere Variante ist die Verwendung desExpression API (die eine hat.Compile() Methode) oderDynamicMethod (was hatCreateDelegate()). Der Schlüssel: Sie müssen eine bekommengetippt delegieren und mit aufrufengetippt aufrufen (nicht.DynamicInvoke).

In komplexeren Fällen, in denen Sie ganze Typen generieren, sollten Sie eine bekannte Schnittstelle implementieren, z.

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

nochmal; Nach der ersten Besetzung (die sehr billig ist) können Sie einfach statischen Code verwenden:

foo.Bar();

Tunnicht benutzensomeDelegate.DynamicInvoke(...) odersomeMethod.Invoke(...) wenn Sie nach irgendeiner Art von Leistung sind.

Ich verwende die EXE-Datei (WinForm, nicht die Konsole) außerhalb von VS. Es ist für alle CPUs (.Net 4.0) mit eingeschalteten Optimierungen kompiliert. Ich verwende Win 7 64-Bit, Core 2 Duo-CPU. IamIC
Vielen Dank @MarcGravell :) Das löst es ... Ich werde diese Lösungen in MSDN nachschlagen. IamIC
@MarcGravell aus reinem Interesse, ich habe dies auf einem i7 und unter .Net 4.5 erneut getestet, und es ist jetzt etwas mehr als 1,4 X Unterschied. Gleicher Code. IamIC
@RuneFS Weitere Informationen finden Sie hier:msmvps.com/blogs/jon_skeet/archive/2008/08/09/… sloth
3

Compileroption "Optimize" verbessern:

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

Verwandte Fragen