Kompilierte C # Lambda Expressions-Leistung

91

Betrachten Sie die folgende einfache Manipulation einer Sammlung:

static List<int> x = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var result = x.Where(i => i % 2 == 0).Where(i => i > 5);

Verwenden wir jetzt Ausdrücke. Der folgende Code entspricht in etwa:

static void UsingLambda() {
    Func<IEnumerable<int>, IEnumerable<int>> lambda = l => l.Where(i => i % 2 == 0).Where(i => i > 5);
    var t0 = DateTime.Now.Ticks;
    for (int j = 1; j < MAX; j++) 
        var sss = lambda(x).ToList();

    var tn = DateTime.Now.Ticks;
    Console.WriteLine("Using lambda: {0}", tn - t0);
}

Aber ich möchte den Ausdruck on-the-fly erstellen, daher hier ein neuer Test:

static void UsingCompiledExpression() {
    var f1 = (Expression<Func<IEnumerable<int>, IEnumerable<int>>>)(l => l.Where(i => i % 2 == 0));
    var f2 = (Expression<Func<IEnumerable<int>, IEnumerable<int>>>)(l => l.Where(i => i > 5));
    var argX = Expression.Parameter(typeof(IEnumerable<int>), "x");
    var f3 = Expression.Invoke(f2, Expression.Invoke(f1, argX));
    var f = Expression.Lambda<Func<IEnumerable<int>, IEnumerable<int>>>(f3, argX);

    var c3 = f.Compile();

    var t0 = DateTime.Now.Ticks;
    for (int j = 1; j < MAX; j++) 
        var sss = c3(x).ToList();

    var tn = DateTime.Now.Ticks;
    Console.WriteLine("Using lambda compiled: {0}", tn - t0);
}

Natürlich ist es nicht genau wie oben, also um fair zu sein, ändere ich das erste leicht:

static void UsingLambdaCombined() {
    Func<IEnumerable<int>, IEnumerable<int>> f1 = l => l.Where(i => i % 2 == 0);
    Func<IEnumerable<int>, IEnumerable<int>> f2 = l => l.Where(i => i > 5);
    Func<IEnumerable<int>, IEnumerable<int>> lambdaCombined = l => f2(f1(l));
    var t0 = DateTime.Now.Ticks;
    for (int j = 1; j < MAX; j++) 
        var sss = lambdaCombined(x).ToList();

    var tn = DateTime.Now.Ticks;
    Console.WriteLine("Using lambda combined: {0}", tn - t0);
}

Jetzt kommen die Ergebnisse für MAX = 100000, VS2008, Debugging ON:

Using lambda compiled: 23437500
Using lambda:           1250000
Using lambda combined:  1406250

Und mit ausgeschaltetem Debugging:

Using lambda compiled: 21718750
Using lambda:            937500
Using lambda combined:  1093750

Überraschung . Der kompilierte Ausdruck ist ungefähr 17x langsamer als die anderen Alternativen. Nun kommen hier die Fragen:

  1. Vergleiche ich nicht äquivalente Ausdrücke?
  2. Gibt es einen Mechanismus, mit dem .NET den kompilierten Ausdruck "optimiert"?
  3. Wie drücke ich denselben Kettenaufruf l.Where(i => i % 2 == 0).Where(i => i > 5);programmgesteuert aus?

Noch ein paar Statistiken. Visual Studio 2010, Debugging EIN, Optimierungen AUS:

Using lambda:           1093974
Using lambda compiled: 15315636
Using lambda combined:   781410

Debugging EIN, Optimierungen EIN:

Using lambda:            781305
Using lambda compiled: 15469839
Using lambda combined:   468783

Debugging AUS, Optimierungen EIN:

Using lambda:            625020
Using lambda compiled: 14687970
Using lambda combined:   468765

Neue Überraschung. Der Wechsel von VS2008 (C # 3) zu VS2010 (C # 4) macht das UsingLambdaCombinedLambda schneller als das native.


Ok, ich habe einen Weg gefunden, die kompilierte Lambda-Leistung um mehr als eine Größenordnung zu verbessern. Hier ist ein Tipp; Nach dem Ausführen des Profilers werden 92% der Zeit für Folgendes aufgewendet:

System.Reflection.Emit.DynamicMethod.CreateDelegate(class System.Type, object)

Hmmmm ... Warum wird in jeder Iteration ein neuer Delegat erstellt? Ich bin nicht sicher, aber die Lösung folgt in einem separaten Beitrag.

Hugo Sereno Ferreira
quelle
3
Werden diese Timings nicht in Visual Studio ausgeführt? Wenn ja, wiederholen Sie die Timings mit einem Release-Modus und führen Sie sie ohne Debugging aus (dh Strg + F5 in Visual Studio oder über die Befehlszeile). StopwatchZiehen Sie auch in Betracht, für Timings anstatt zu verwenden DateTime.Now.
Jim Mischel
12
Ich weiß nicht, warum es langsamer ist, aber Ihre Benchmark-Technik ist nicht sehr gut. Zunächst einmal ist DateTime.Now nur auf 1/64 Sekunde genau, sodass Ihr Messungsrundungsfehler groß ist. Verwenden Sie stattdessen Stoppuhr. es ist auf einige Nanosekunden genau. Zweitens messen Sie sowohl die Zeit, um den Code (den ersten Anruf) als auch jeden nachfolgenden Anruf einzugeben. das kann Durchschnittswerte abwerfen. (Obwohl in diesem Fall ein MAX von hunderttausend wahrscheinlich ausreicht, um die Jit-Belastung zu mitteln, ist es dennoch eine schlechte Praxis, sie in den Durchschnitt aufzunehmen.)
Eric Lippert
7
@Eric, Rundungsfehler können nur vorhanden sein, wenn in jeder Operation DateTime.Now.Ticks verwendet wird. Vor dem Start und nach dem Ende sind die Millisekundenzahlen hoch genug, um Leistungsunterschiede anzuzeigen.
Akash Kava
1
Wenn Sie eine Stoppuhr verwenden, empfehle ich, diesen Artikel zu befolgen
Zach Green
1
@Eric, obwohl ich damit einverstanden bin, dass es nicht die genaueste verfügbare Messtechnik ist, sprechen wir über eine Größenordnung der Differenz. MAX ist hoch genug, um signifikante Abweichungen zu reduzieren.
Hugo Sereno Ferreira

Antworten:

43

Könnte es sein, dass die inneren Lambdas nicht zusammengestellt werden?!? Hier ist ein Proof of Concept:

static void UsingCompiledExpressionWithMethodCall() {
        var where = typeof(Enumerable).GetMember("Where").First() as System.Reflection.MethodInfo;
        where = where.MakeGenericMethod(typeof(int));
        var l = Expression.Parameter(typeof(IEnumerable<int>), "l");
        var arg0 = Expression.Parameter(typeof(int), "i");
        var lambda0 = Expression.Lambda<Func<int, bool>>(
            Expression.Equal(Expression.Modulo(arg0, Expression.Constant(2)),
                             Expression.Constant(0)), arg0).Compile();
        var c1 = Expression.Call(where, l, Expression.Constant(lambda0));
        var arg1 = Expression.Parameter(typeof(int), "i");
        var lambda1 = Expression.Lambda<Func<int, bool>>(Expression.GreaterThan(arg1, Expression.Constant(5)), arg1).Compile();
        var c2 = Expression.Call(where, c1, Expression.Constant(lambda1));

        var f = Expression.Lambda<Func<IEnumerable<int>, IEnumerable<int>>>(c2, l);

        var c3 = f.Compile();

        var t0 = DateTime.Now.Ticks;
        for (int j = 1; j < MAX; j++)
        {
            var sss = c3(x).ToList();
        }

        var tn = DateTime.Now.Ticks;
        Console.WriteLine("Using lambda compiled with MethodCall: {0}", tn - t0);
    }

Und jetzt sind die Zeiten:

Using lambda:                            625020
Using lambda compiled:                 14687970
Using lambda combined:                   468765
Using lambda compiled with MethodCall:   468765

Woot! Es ist nicht nur schnell, es ist auch schneller als das einheimische Lambda. ( Kratzkopf ).


Natürlich ist der obige Code einfach zu schmerzhaft zum Schreiben. Lassen Sie uns eine einfache Magie machen:

static void UsingCompiledConstantExpressions() {
    var f1 = (Func<IEnumerable<int>, IEnumerable<int>>)(l => l.Where(i => i % 2 == 0));
    var f2 = (Func<IEnumerable<int>, IEnumerable<int>>)(l => l.Where(i => i > 5));
    var argX = Expression.Parameter(typeof(IEnumerable<int>), "x");
    var f3 = Expression.Invoke(Expression.Constant(f2), Expression.Invoke(Expression.Constant(f1), argX));
    var f = Expression.Lambda<Func<IEnumerable<int>, IEnumerable<int>>>(f3, argX);

    var c3 = f.Compile();

    var t0 = DateTime.Now.Ticks;
    for (int j = 1; j < MAX; j++) {
        var sss = c3(x).ToList();
    }

    var tn = DateTime.Now.Ticks;
    Console.WriteLine("Using lambda compiled constant: {0}", tn - t0);
}

Und einige Timings, VS2010, Optimierungen EIN, Debuggen AUS:

Using lambda:                            781260
Using lambda compiled:                 14687970
Using lambda combined:                   468756
Using lambda compiled with MethodCall:   468756
Using lambda compiled constant:          468756

Jetzt könnte man argumentieren, dass ich nicht den gesamten Ausdruck dynamisch generiere; nur die Verkettungsaufrufe. Aber im obigen Beispiel generiere ich den gesamten Ausdruck. Und die Zeiten stimmen überein. Dies ist nur eine Verknüpfung, um weniger Code zu schreiben.


Nach meinem Verständnis geht es darum, dass die .Compile () -Methode die Kompilierungen nicht an innere Lambdas weitergibt und somit den ständigen Aufruf von CreateDelegate. Aber um dies wirklich zu verstehen, würde ich gerne einen .NET-Guru dazu bringen, ein wenig über die internen Dinge zu kommentieren.

Und warum , oh warum ist das jetzt schneller als ein einheimisches Lambda?

Hugo Sereno Ferreira
quelle
1
Ich denke darüber nach, meine eigene Antwort zu akzeptieren, da es die mit den meisten Stimmen ist. Soll ich etwas länger warten?
Hugo Sereno Ferreira
Wenn Sie wissen
Blaisorblade
Was den Grund betrifft, warum das dynamisch kompilierte Lambda schneller ist, vermute ich, dass die Verwendung von Lambda, das zuerst ausgeführt wird, mit dem JIT-Code bestraft wird.
Oskar Berggren
Ich weiß nicht, was passiert. Als ich einmal den kompilierten Ausdruck und das erstellte Element zum Setzen und Abrufen von Feldern und Eigenschaften getestet habe, war das erstellte Element für Eigenschaften viel schneller, aber das kompilierte war für Felder etwas schneller
nawfal
10

Kürzlich habe ich eine fast identische Frage gestellt:

Leistung des zum Delegieren kompilierten Ausdrucks

Die Lösung für mich war , dass ich nicht nennen sollte Compileauf das Expression, aber das nenne ich sollte CompileToMethoddarauf und kompilieren die Expressionzu einem staticVerfahren in einer dynamischen Montage.

Wie so:

var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
  new AssemblyName("MyAssembly_" + Guid.NewGuid().ToString("N")), 
  AssemblyBuilderAccess.Run);

var moduleBuilder = assemblyBuilder.DefineDynamicModule("Module");

var typeBuilder = moduleBuilder.DefineType("MyType_" + Guid.NewGuid().ToString("N"), 
  TypeAttributes.Public));

var methodBuilder = typeBuilder.DefineMethod("MyMethod", 
  MethodAttributes.Public | MethodAttributes.Static);

expression.CompileToMethod(methodBuilder);

var resultingType = typeBuilder.CreateType();

var function = Delegate.CreateDelegate(expression.Type,
  resultingType.GetMethod("MyMethod"));

Es ist jedoch nicht ideal. Ich bin nicht ganz sicher, für welche Typen dies genau gilt, aber ich denke, dass Typen, die vom Delegaten als Parameter verwendet oder vom Delegaten zurückgegeben werden , publicnicht generisch sein müssen. Es muss nicht generisch sein, da generische Typen anscheinend auf System.__Canoneinen internen Typ zugreifen, der von .NET unter der Haube für generische Typen verwendet wird, und dies gegen die publicRegel "muss eine Typregel sein" verstößt .

Für diese Typen können Sie den scheinbar langsameren verwenden Compile. Ich erkenne sie folgendermaßen:

private static bool IsPublicType(Type t)
{

  if ((!t.IsPublic && !t.IsNestedPublic) || t.IsGenericType)
  {
    return false;
  }

  int lastIndex = t.FullName.LastIndexOf('+');

  if (lastIndex > 0)
  {
    var containgTypeName = t.FullName.Substring(0, lastIndex);

    var containingType = Type.GetType(containgTypeName + "," + t.Assembly);

    if (containingType != null)
    {
      return containingType.IsPublic;
    }

    return false;
  }
  else
  {
    return t.IsPublic;
  }
}

Aber wie gesagt, das ist nicht ideal und ich würde immer noch gerne wissen, warum das Kompilieren einer Methode zu einer dynamischen Assembly manchmal eine Größenordnung schneller ist. Und ich sage manchmal, weil ich auch Fälle gesehen habe, in denen eine ExpressionKompilierung Compilegenauso schnell ist wie eine normale Methode. Siehe meine Frage dazu.

Oder wenn jemand eine Möglichkeit kennt, die publicEinschränkung "Keine Nicht- Typen" mit der dynamischen Assembly zu umgehen , ist dies ebenfalls willkommen.

JulianR
quelle
4

Ihre Ausdrücke sind nicht gleichwertig und Sie erhalten daher verzerrte Ergebnisse. Ich habe einen Prüfstand geschrieben, um dies zu testen. Die Tests umfassen den regulären Lambda-Aufruf, den äquivalenten kompilierten Ausdruck, einen handgemachten äquivalenten kompilierten Ausdruck sowie komponierte Versionen. Dies sollten genauere Zahlen sein. Interessanterweise sehe ich keine großen Unterschiede zwischen der einfachen und der komponierten Version. Und die kompilierten Ausdrücke sind natürlich langsamer, aber nur sehr wenig. Sie benötigen eine ausreichend große Eingabe- und Iterationszahl, um einige gute Zahlen zu erhalten. Es macht einen Unterschied.

Was Ihre zweite Frage betrifft, weiß ich nicht, wie Sie mehr Leistung daraus ziehen können, daher kann ich Ihnen dort nicht helfen. Es sieht so gut aus, wie es nur geht.

Meine Antwort auf Ihre dritte Frage finden Sie in der HandMadeLambdaExpression()Methode. Aufgrund der Erweiterungsmethoden nicht der am einfachsten zu erstellende Ausdruck, aber machbar.

using System;
using System.Collections.Generic;
using System.Linq;

using System.Diagnostics;
using System.Linq.Expressions;

namespace ExpressionBench
{
    class Program
    {
        static void Main(string[] args)
        {
            var values = Enumerable.Range(0, 5000);
            var lambda = GetLambda();
            var lambdaExpression = GetLambdaExpression().Compile();
            var handMadeLambdaExpression = GetHandMadeLambdaExpression().Compile();
            var composed = GetComposed();
            var composedExpression = GetComposedExpression().Compile();
            var handMadeComposedExpression = GetHandMadeComposedExpression().Compile();

            DoTest("Lambda", values, lambda);
            DoTest("Lambda Expression", values, lambdaExpression);
            DoTest("Hand Made Lambda Expression", values, handMadeLambdaExpression);
            Console.WriteLine();
            DoTest("Composed", values, composed);
            DoTest("Composed Expression", values, composedExpression);
            DoTest("Hand Made Composed Expression", values, handMadeComposedExpression);
        }

        static void DoTest<TInput, TOutput>(string name, TInput sequence, Func<TInput, TOutput> operation, int count = 1000000)
        {
            for (int _ = 0; _ < 1000; _++)
                operation(sequence);
            var sw = Stopwatch.StartNew();
            for (int _ = 0; _ < count; _++)
                operation(sequence);
            sw.Stop();
            Console.WriteLine("{0}:", name);
            Console.WriteLine("  Elapsed: {0,10} {1,10} (ms)", sw.ElapsedTicks, sw.ElapsedMilliseconds);
            Console.WriteLine("  Average: {0,10} {1,10} (ms)", decimal.Divide(sw.ElapsedTicks, count), decimal.Divide(sw.ElapsedMilliseconds, count));
        }

        static Func<IEnumerable<int>, IList<int>> GetLambda()
        {
            return v => v.Where(i => i % 2 == 0).Where(i => i > 5).ToList();
        }

        static Expression<Func<IEnumerable<int>, IList<int>>> GetLambdaExpression()
        {
            return v => v.Where(i => i % 2 == 0).Where(i => i > 5).ToList();
        }

        static Expression<Func<IEnumerable<int>, IList<int>>> GetHandMadeLambdaExpression()
        {
            var enumerableMethods = typeof(Enumerable).GetMethods();
            var whereMethod = enumerableMethods
                .Where(m => m.Name == "Where")
                .Select(m => m.MakeGenericMethod(typeof(int)))
                .Where(m => m.GetParameters()[1].ParameterType == typeof(Func<int, bool>))
                .Single();
            var toListMethod = enumerableMethods
                .Where(m => m.Name == "ToList")
                .Select(m => m.MakeGenericMethod(typeof(int)))
                .Single();

            // helpers to create the static method call expressions
            Func<Expression, ParameterExpression, Func<ParameterExpression, Expression>, Expression> WhereExpression =
                (instance, param, body) => Expression.Call(whereMethod, instance, Expression.Lambda(body(param), param));
            Func<Expression, Expression> ToListExpression =
                instance => Expression.Call(toListMethod, instance);

            //return v => v.Where(i => i % 2 == 0).Where(i => i > 5).ToList();
            var exprParam = Expression.Parameter(typeof(IEnumerable<int>), "v");
            var expr0 = WhereExpression(exprParam,
                Expression.Parameter(typeof(int), "i"),
                i => Expression.Equal(Expression.Modulo(i, Expression.Constant(2)), Expression.Constant(0)));
            var expr1 = WhereExpression(expr0,
                Expression.Parameter(typeof(int), "i"),
                i => Expression.GreaterThan(i, Expression.Constant(5)));
            var exprBody = ToListExpression(expr1);
            return Expression.Lambda<Func<IEnumerable<int>, IList<int>>>(exprBody, exprParam);
        }

        static Func<IEnumerable<int>, IList<int>> GetComposed()
        {
            Func<IEnumerable<int>, IEnumerable<int>> composed0 =
                v => v.Where(i => i % 2 == 0);
            Func<IEnumerable<int>, IEnumerable<int>> composed1 =
                v => v.Where(i => i > 5);
            Func<IEnumerable<int>, IList<int>> composed2 =
                v => v.ToList();
            return v => composed2(composed1(composed0(v)));
        }

        static Expression<Func<IEnumerable<int>, IList<int>>> GetComposedExpression()
        {
            Expression<Func<IEnumerable<int>, IEnumerable<int>>> composed0 =
                v => v.Where(i => i % 2 == 0);
            Expression<Func<IEnumerable<int>, IEnumerable<int>>> composed1 =
                v => v.Where(i => i > 5);
            Expression<Func<IEnumerable<int>, IList<int>>> composed2 =
                v => v.ToList();
            var exprParam = Expression.Parameter(typeof(IEnumerable<int>), "v");
            var exprBody = Expression.Invoke(composed2, Expression.Invoke(composed1, Expression.Invoke(composed0, exprParam)));
            return Expression.Lambda<Func<IEnumerable<int>, IList<int>>>(exprBody, exprParam);
        }

        static Expression<Func<IEnumerable<int>, IList<int>>> GetHandMadeComposedExpression()
        {
            var enumerableMethods = typeof(Enumerable).GetMethods();
            var whereMethod = enumerableMethods
                .Where(m => m.Name == "Where")
                .Select(m => m.MakeGenericMethod(typeof(int)))
                .Where(m => m.GetParameters()[1].ParameterType == typeof(Func<int, bool>))
                .Single();
            var toListMethod = enumerableMethods
                .Where(m => m.Name == "ToList")
                .Select(m => m.MakeGenericMethod(typeof(int)))
                .Single();

            Func<ParameterExpression, Func<ParameterExpression, Expression>, Expression> LambdaExpression =
                (param, body) => Expression.Lambda(body(param), param);
            Func<Expression, ParameterExpression, Func<ParameterExpression, Expression>, Expression> WhereExpression =
                (instance, param, body) => Expression.Call(whereMethod, instance, Expression.Lambda(body(param), param));
            Func<Expression, Expression> ToListExpression =
                instance => Expression.Call(toListMethod, instance);

            var composed0 = LambdaExpression(Expression.Parameter(typeof(IEnumerable<int>), "v"),
                v => WhereExpression(
                    v,
                    Expression.Parameter(typeof(int), "i"),
                    i => Expression.Equal(Expression.Modulo(i, Expression.Constant(2)), Expression.Constant(0))));
            var composed1 = LambdaExpression(Expression.Parameter(typeof(IEnumerable<int>), "v"),
                v => WhereExpression(
                    v,
                    Expression.Parameter(typeof(int), "i"),
                    i => Expression.GreaterThan(i, Expression.Constant(5))));
            var composed2 = LambdaExpression(Expression.Parameter(typeof(IEnumerable<int>), "v"),
                v => ToListExpression(v));

            var exprParam = Expression.Parameter(typeof(IEnumerable<int>), "v");
            var exprBody = Expression.Invoke(composed2, Expression.Invoke(composed1, Expression.Invoke(composed0, exprParam)));
            return Expression.Lambda<Func<IEnumerable<int>, IList<int>>>(exprBody, exprParam);
        }
    }
}

Und die Ergebnisse auf meiner Maschine:

Lambda:
  Abgelaufen: 340971948 123230 (ms)
  Durchschnitt: 340,971948 0,12323 (ms)
Lambda-Ausdruck:
  Abgelaufen: 357077202 129051 (ms)
  Durchschnitt: 357,077202 0,129051 (ms)
Handgemachter Lambda-Ausdruck:
  Abgelaufen: 345029281 124696 (ms)
  Durchschnitt: 345,029281 0,124696 (ms)

Zusammengesetzt:
  Abgelaufen: 340409238 123027 (ms)
  Durchschnitt: 340,409238 0,123027 (ms)
Komponierter Ausdruck:
  Abgelaufen: 350800599 126782 (ms)
  Durchschnitt: 350.800599 0,126782 (ms)
Handgemachter komponierter Ausdruck:
  Abgelaufen: 352811359 127509 (ms)
  Durchschnitt: 352,811359 0,127509 (ms)
Jeff Mercado
quelle
3

Die kompilierte Lambda-Leistung über Delegaten ist möglicherweise langsamer, da kompilierter Code zur Laufzeit möglicherweise nicht optimiert wird. Der manuell geschriebene und über den C # -Compiler kompilierte Code wird jedoch optimiert.

Zweitens bedeuten mehrere Lambda-Ausdrücke mehrere anonyme Methoden, und das Aufrufen jeder dieser Methoden erfordert wenig zusätzliche Zeit für die Bewertung einer geraden Methode. Zum Beispiel anrufen

Console.WriteLine(x);

und

Action x => Console.WriteLine(x);
x(); // this means two different calls..

sind unterschiedlich, und mit dem zweiten ist etwas mehr Overhead erforderlich, als aus Sicht des Compilers, es sind tatsächlich zwei verschiedene Aufrufe. Rufen Sie zuerst x selbst auf und dann innerhalb der Anweisung von x.

Ihr kombinierter Lambda hat also sicherlich eine geringe langsame Leistung gegenüber einem einzelnen Lambda-Ausdruck.

Dies ist unabhängig von der Ausführung im Inneren, da Sie immer noch die korrekte Logik auswerten, aber zusätzliche Schritte für den Compiler hinzufügen.

Selbst nachdem der Ausdrucksbaum kompiliert wurde, wird er nicht optimiert, und seine kleine komplexe Struktur bleibt erhalten. Wenn er ausgewertet und aufgerufen wird, kann es zu einer zusätzlichen Validierung, Nullprüfung usw. kommen, was die Leistung kompilierter Lambda-Ausdrücke verlangsamen kann.

Akash Kava
quelle
2
Wenn Sie genau hinschauen, UsingLambdaCombinedkombiniert der Test mehrere Lambda-Funktionen und seine Leistung ist sehr nahe UsingLambda. In Bezug auf die Optimierungen war ich davon überzeugt, dass sie von der JIT-Engine verarbeitet werden und daher zur Laufzeit generierter Code (nach der Kompilierung) auch Ziel von JIT-Optimierungen sein würde.
Hugo Sereno Ferreira
1
JIT-Optimierung und Kompilierungszeitoptimierung sind zwei verschiedene Dinge, die Sie in den Projekteinstellungen deaktivieren können. Zweitens wird die Ausdruckskompilierung wahrscheinlich eine dynamische MSIL ausgeben, die wiederum etwas langsamer sein wird, da ihre Logik und die Abfolge der Operationen je nach Bedarf Nullprüfungen und Gültigkeit enthalten. Sie können im Reflektor nachsehen, wie es kompiliert wird.
Akash Kava
2
Obwohl Ihre Argumentation stichhaltig ist, muss ich Ihnen in diesem speziellen Problem nicht zustimmen (dh der Unterschied in der Größenordnung ist nicht auf statische Kompilierung zurückzuführen). Erstens, denn wenn Sie die Optimierungen zur Kompilierungszeit tatsächlich deaktivieren, ist der Unterschied immer noch beträchtlich. Zweitens, weil ich bereits einen Weg gefunden habe, die dynamische Erzeugung so zu optimieren, dass sie nur geringfügig langsamer ist. Lassen Sie mich versuchen, das "Warum" zu verstehen, und ich werde die Ergebnisse veröffentlichen.
Hugo Sereno Ferreira