Warum wird Func &lt;&gt; aus Expression erstellt? <Func<> &gt; langsamer als Func &lt;&gt; direkt deklariert?

c# delegates expression expression-trees func

Frage

Warum wird ein Func<> aus einem Expression<Func<>> über .Compile () wesentlich langsamer erzeugt als nur mit einem direkt deklarierten Func<> ?

Ich habe gerade von der Verwendung eines Func<IInterface, object> , das direkt in ein Expression<Func<IInterface, object>> in einer App, an der ich arbeite, Expression<Func<IInterface, object>> und festgestellt, dass die Leistung Expression<Func<IInterface, object>> .

Ich habe gerade einen kleinen Test gemacht, und der Func<> der aus einem Ausdruck erstellt wurde, nimmt "fast" die doppelte Zeit eines direkt erklärten Func<> .

Auf meinem Rechner dauert die Direct Func<> etwa 7,5 Sekunden und der Expression<Func<>> dauert etwa 12,6 Sekunden.

Hier ist der Testcode, den ich verwendet habe (läuft Net 4.0)

// Direct
Func<int, Foo> test1 = x => new Foo(x * 2);

int counter1 = 0;

Stopwatch s1 = new Stopwatch();
s1.Start();
for (int i = 0; i < 300000000; i++)
{
 counter1 += test1(i).Value;
}
s1.Stop();
var result1 = s1.Elapsed;



// Expression . Compile()
Expression<Func<int, Foo>> expression = x => new Foo(x * 2);
Func<int, Foo> test2 = expression.Compile();

int counter2 = 0;

Stopwatch s2 = new Stopwatch();
s2.Start();
for (int i = 0; i < 300000000; i++)
{
 counter2 += test2(i).Value;
}
s2.Stop();
var result2 = s2.Elapsed;



public class Foo
{
 public Foo(int i)
 {
  Value = i;
 }
 public int Value { get; set; }
}

Wie kann ich die Leistung zurückbekommen?

Gibt es irgendetwas, was ich tun kann, um den Func<> der aus dem Expression<Func<>> , so auszuführen, wie er direkt erklärt wird?

Akzeptierte Antwort

Wie andere bereits erwähnt haben, verursacht der Overhead beim Aufruf eines dynamischen Delegaten Ihre Verlangsamung. Auf meinem Computer liegt der Overhead bei 12ns mit meiner CPU bei 3GHz. Um das zu umgehen, laden Sie die Methode aus einer kompilierten Assembly wie folgt:

var ab = AppDomain.CurrentDomain.DefineDynamicAssembly(
             new AssemblyName("assembly"), AssemblyBuilderAccess.Run);
var mod = ab.DefineDynamicModule("module");
var tb = mod.DefineType("type", TypeAttributes.Public);
var mb = tb.DefineMethod(
             "test3", MethodAttributes.Public | MethodAttributes.Static);
expression.CompileToMethod(mb);
var t = tb.CreateType();
var test3 = (Func<int, Foo>)Delegate.CreateDelegate(
                typeof(Func<int, Foo>), t.GetMethod("test3"));

int counter3 = 0;
Stopwatch s3 = new Stopwatch();
s3.Start();
for (int i = 0; i < 300000000; i++)
{
    counter3 += test3(i).Value;
}
s3.Stop();
var result3 = s3.Elapsed;

Wenn ich den obigen Code hinzufüge, ist result3 immer nur einen Bruchteil einer Sekunde höher als result1 , für etwa 1ns Overhead.

Warum sollte man sich also mit einem kompilierten Lambda ( test2 ) beschäftigen, wenn man einen schnelleren Delegierten ( test3 ) haben kann? Da das Erstellen der dynamischen Assembly im Allgemeinen viel mehr Aufwand verursacht und Sie nur 10-20ns bei jedem Aufruf speichert.


Beliebte Antwort

(Dies ist keine richtige Antwort, sondern Material, das dazu beitragen soll, die Antwort zu finden.)

Statistiken gesammelt von Mono 2.6.7 - Debian Lenny - Linux 2.6.26 i686 - 2.80GHz single core:

      Func: 00:00:23.6062578
Expression: 00:00:23.9766248

Auf Mono scheinen also beide Mechanismen gleichwertige IL zu erzeugen.

Dies ist die von Monos gmcs für die anonyme Methode erzeugte IL:

      Func: 00:00:23.6062578
Expression: 00:00:23.9766248

Ich werde an dem Extrahieren der IL arbeiten, die von dem Expression-Compiler generiert wird.




Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Ist diese KB legal? Ja, lerne warum
Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Ist diese KB legal? Ja, lerne warum