Ich habe in eine seltsame Leistungseinbuße führen, das ich gekocht habe auf diesen Code unten:Leistungseinbuße, wenn Generic.List <T> .Add die die letzte Anweisung in einer Funktion und tailcall Optimierung ist auf
[<Struct>]
type Vector3(x: float32, y: float32, z: float32) =
member this.X = x
member this.Y = y
member this.Z = z
type Data(n: int) =
let positions = System.Collections.Generic.List<Vector3>()
let add j = positions.Add (Vector3(j, j, j))
let add1 j = positions.Add (Vector3(j, j, j));()
member this.UseAdd() = for i = 1 to n do add (float32 i)
member this.UseAdd1() = for i = 1 to n do add1 (float32 i)
let timeIt name (f: unit -> unit) =
let timer = System.Diagnostics.Stopwatch.StartNew()
f()
printfn "%s: %ims" name (int timer.ElapsedMilliseconds)
let test() =
for i = 1 to 3 do timeIt "ADD" (fun() -> Data(1000000).UseAdd())
for i = 1 to 3 do timeIt "ADD1" (fun() -> Data(1000000).UseAdd1())
[<EntryPoint>]
let main argv =
test()
0
Der Unterschied zwischen add
und add1
ist das extra ()
am Ende.
Wenn ich es als x64 Releasebuild bauen mit F # 3.1 auf .NET 4.5.1 bekomme ich diese Ausgabe:
ADD: 461ms
ADD: 457ms
ADD: 450ms
ADD1: 25ms
ADD1: 26ms
ADD1: 16ms
Da die Art der List<T>.Add
ist T -> unit
Ich würde erwarten, dass add
und add1
sollten identisch verhalten .
Mit ildasm Ich habe festgestellt, dass add
kompiliert (einschließlich nur der relevante Teil)
IL_000a: newobj instance void Program/Vector3::.ctor(float32,
float32,
float32)
IL_000f: tail.
IL_0011: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<valuetype Program/Vector3>::Add(!0)
während add1
in
IL_000a: newobj instance void Program/Vector3::.ctor(float32,
float32,
float32)
IL_000f: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<valuetype Program/Vector3>::Add(!0)
das heißt ohne "Endaufruf". Wenn ich also die Tail Call Optimierung abstelle, laufen sowohl add
als auch add1
mit der gleichen Geschwindigkeit.
Warum bewirkt der Befehl tail.
, dass der Funktionsaufruf so viel langsamer ist? Ist das ein Fehler oder eine Funktion?
EDIT: Dies ist der ursprüngliche Code hier bemerkte ich dieses Verhalten. Wenn der true
Wert am Ende gelöscht wird, zeigt es den gleichen Leistungsabfall wie der obige Code.
Interessant, es tritt nur in .NET 4+, und die Diskrepanz ist auf x86 oder bei Verwendung anderer Datentypen in der 'List' viel kleiner. –
@DaxFohl Ja, ich habe festgestellt, dass es auch auf x86 niedriger ist.Aber ich brauche meinen Code 64-Bit, deshalb habe ich diese Daten enthalten. – Dave
Hast du es auf RyuJIT oder auf normalem JIT ausgeführt? Der Artikel, den Sie verlinkt haben, scheint mit dem alten verwandt zu sein. –