gpt4 book ai didi

.net - 当 Generic.List.Add 是函数中的最后一个语句并且尾调用优化开启时的性能损失

转载 作者:行者123 更新时间:2023-12-04 08:48:31 25 4
gpt4 key购买 nike

我遇到了一个奇怪的性能损失,我归结为以下代码:

[<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
add的区别和 add1是额外的 ()在末尾。

当我在 .NET 4.5.1 上使用 F# 3.1 将其构建为 x64 Release 版本时,我得到以下输出:
ADD: 461ms
ADD: 457ms
ADD: 450ms
ADD1: 25ms
ADD1: 26ms
ADD1: 16ms

自类型 List<T>.AddT -> unit我希望 addadd1应该表现相同。

使用 ILdasm 我发现 add编译为(仅包括相关部分)
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)

add1进入
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)

即没有“尾声”。所以当我关闭尾调用优化时, addadd1以相同的速度运行。

为什么 tail.指令导致函数调用慢得多?另外,这是一个错误还是一个功能?

编辑:这是我注意到这种行为的原始代码。当 true最后的值被删除,它表现出与上面代码相​​同的性能下降。
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

最佳答案

我想我已经弄清楚问题出在哪里以及为什么我对问题的误解而不是 F# 编译器或 .NET 中的错误。

编码

let add j = positions.Add (Vector3(j, j, j))

大致意思是“从值 List<T>.Add 上的尾调用位置调用 Vector3(j, j, j)”,而
let add1 j = positions.Add (Vector3(j, j, j)); ()

表示“在值 List<T>.Add 上调用 Vector3(j, j, j),然后返回 unit”。

类型方面,与 List<T>.Add 没有区别返回 unit所以我错误地假设了 positions.Add会被调用然后 add将返回值 unit这是 List<T>.Add 的返回值.但是,如 http://blogs.msdn.com/b/clrcodegeneration/archive/2009/05/11/tail-call-improvements-in-net-framework-4.aspx 所述,当尾调用函数的参数不平凡时,JIT 需要执行一些“堆栈魔术”。这就是性能差距的来源。差异非常微妙,但确实存在。

关于.net - 当 Generic.List<T>.Add 是函数中的最后一个语句并且尾调用优化开启时的性能损失,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28649422/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com