Zabrakło mi w dziwny spadku wydajności że mam sprowadzić do tego kodu:Wydajność kara kiedy Generic.List <T> .Add jest ostatnia wypowiedź w funkcji i tailcall optymalizacja jest
[<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
Różnica między add
i add1
jest dodatkowym ()
na końcu.
Kiedy budować go jako kompilacji x64 wydania użyciem F # .NET 3.1 na 4.5.1 uzyskać ten wynik:
ADD: 461ms
ADD: 457ms
ADD: 450ms
ADD1: 25ms
ADD1: 26ms
ADD1: 16ms
Rejestracja rodzaju List<T>.Add
jest T -> unit
Spodziewam się, że add
i add1
powinien zachowywać się identycznie .
Stosując ILdasm Zostało stwierdzone, że add
sporządza się (w tym tylko odpowiedniej części)
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)
podczas add1
do
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)
to znaczy bez "połączenia ogona". Kiedy więc wyłączam optymalizację połączeń końcowych, zarówno add
, jak i add1
działają z tą samą prędkością.
Dlaczego instrukcja tail.
powoduje, że wywołanie funkcji jest znacznie wolniejsze? Czy jest to błąd lub funkcja?
EDYCJA: To jest oryginalny kod tutaj zauważyłem to zachowanie. Kiedy wartość true
na końcu jest opuszczona, wykazuje taki sam spadek wydajności jak powyższy kod.
let makeAtom (ctx: CleanCifContext) (element: CleanCifAtomSiteElement) =
let residue = getResidue ctx element
let position =
Vector3(float32 (element.PositionX.ValueOrFail()), float32 (element.PositionY.ValueOrFail()), float32 (element.PositionZ.ValueOrFail()))
let atom =
CifAtom(id = ctx.Atoms.Count, element = element.ElementSymbol.ValueOrFail(),
residue = residue, serialNumber = element.Id.ValueOrFail(),
name = element.Name.ValueOrFail(), authName = element.AuthName.Value(), altLoc = element.AltLoc.Value(),
occupancy = float32 (element.Occupancy.ValueOrFail()), tempFactor = float32 (element.TempFactor.ValueOrFail()))
ctx.Atoms.Add atom
ctx.Positions.Add position
true
Interesujące, występuje tylko w .NET 4+, a rozbieżność jest znacznie mniejsza na x86 lub podczas korzystania z innych typów danych w "liście". –
@DaxFohl Tak, zauważyłem, że jest również niższy na x86.Ale potrzebuję, aby mój kod był 64-bitowy, dlatego uwzględniłem te dane. – Dave
Czy uruchomiłeś go na RyuJIT lub na zwykłym starym JIT? Artykuł, który łączysz, wydaje się być powiązany ze starym. –