Leistungseinbuße, wenn Generic.ListT.Add die letzte Anweisung in einer Funktion ist und die Optimierung der Rufannahme aktiviert ist

8

Ich habe eine seltsame Leistungseinbuße bekommen, die ich auf diesen Code reduziert habe:

%Vor%

Der Unterschied zwischen add und add1 ist der zusätzliche () am Ende.

Wenn ich es als x64 Release Build mit F # 3.1 auf .NET 4.5.1 erstelle, bekomme ich diese Ausgabe:

%Vor%

Da der Typ von List<T>.Add T -> unit ist, würde ich erwarten, dass add und add1 sich identisch verhalten sollten.

Mit ILdasm habe ich gefunden, dass add kompiliert (einschließlich nur des relevanten Teils)

%Vor%

während add1 in

%Vor%

d. ohne den "Tail Call". Wenn ich die Tail Call Optimierung deaktiviere, laufen sowohl add als auch add1 mit der gleichen Geschwindigkeit.

Warum bewirkt der Befehl tail. , dass der Funktionsaufruf so viel langsamer ist? Ist das auch ein Fehler oder eine Funktion?

EDIT: Dies ist der ursprüngliche Code hier bemerkte ich dieses Verhalten. Wenn der Wert true am Ende gelöscht wird, weist er den gleichen Leistungsabfall auf wie der obige Code.

%Vor%     
Dave 21.02.2015, 18:13
quelle

1 Antwort

2

Ich denke, ich habe herausgefunden, wo das Problem liegt und warum es mein Missverständnis ist, anstatt das Problem im F # Compiler oder .NET zu lösen.

Der Code

%Vor%

bedeutet grob "call List<T>.Add von der hinteren Aufrufposition auf dem Wert Vector3(j, j, j) " während

%Vor%

bedeutet "Rufen Sie List<T>.Add für den Wert Vector3(j, j, j) auf und geben Sie dann unit " zurück.

Typisch gibt es keinen Unterschied, da List<T>.Add unit zurückgibt, also nahm ich fälschlicherweise an, dass positions.Add aufgerufen würde und dann add den Wert unit zurückgab, was der Rückgabewert von List<T>.Add ist . Wie jedoch unter Ссылка angegeben , das JIT muss etwas "Stapelmagie" ausführen, wenn die Argumente der Schwanzfunktion nicht-trivial sind. Und hier kommt die Leistungslücke. Der Unterschied ist sehr subtil, aber es ist da.

    
Dave 22.02.2015 14:22
quelle

Tags und Links