F# 堆栈溢出,尽管尾部调用位置不同,但仅在64位
起源于,我有一个小F#代码()根据正态分布生成随机值:F# 堆栈溢出,尽管尾部调用位置不同,但仅在64位,f#,64-bit,stack-overflow,32-bit,tail-recursion,F#,64 Bit,Stack Overflow,32 Bit,Tail Recursion,起源于,我有一个小F#代码()根据正态分布生成随机值: // val nextSingle : (unit -> float32) let nextSingle = let r = System.Random() r.NextDouble >> float32 // val gauss : (float32 -> float32 -> seq<float32>) let gauss mean stdDev = let rec g
// val nextSingle : (unit -> float32)
let nextSingle =
let r = System.Random()
r.NextDouble >> float32
// val gauss : (float32 -> float32 -> seq<float32>)
let gauss mean stdDev =
let rec gauss ready = seq {
match ready with
| Some spare ->
yield spare * stdDev + mean
yield! gauss None
| _ ->
let rec loop () =
let u = nextSingle() * 2.f - 1.f
let v = nextSingle() * 2.f - 1.f
let s = pown u 2 + pown v 2
if s >= 1.f || s = 0.f then loop() else
u, v, s
let u, v, s = loop()
let mul = (*)(sqrt(-2.f * log s / s))
yield mul u * stdDev + mean
yield! mul v |> Some |> gauss
}
gauss None
//val nextingle:(单位->浮动32)
让我们继续=
设r=System.Random()
r、 下一步加倍>>浮动32
//val gauss:(浮动32->浮动32->seq)
设gauss表示stdDev=
让rec gauss ready=seq{
匹配
|一些备用->
备用产量*标准差+平均值
屈服!高斯零
| _ ->
让rec循环()=
设u=nextingle()*2.f-1.f
设v=nextingle()*2.f-1.f
设s=pown u 2+pown v 2
如果s>=1.f | | s=0.f,则循环()else
u、 v,s
设u,v,s=loop()
设mul=(*)(sqrt(-2.f*logs/s))
产量倍数*标准差+平均值
屈服!mul v |>一些|>高斯
}
高斯无
在我看来,这应该只在尾部调用位置调用自身,因此在启用TCO时,不会导致StackOverflowException
。但在运行64位。运行32位(即“项目设置”中的“首选32位”复选框)时不会显示此选项
我使用的是.NETFramework4.5.2和F#4.4.0.0
有人能解释一下问题的原因吗?看起来像是编译器序列表达式编译机制中的一个bug。这是一个简化的复制:
let rec loop r = seq {
if r > 0 then
let rec unused() = unused()
yield r
yield! loop r
}
printfn "%i" (Seq.nth 10000000 (loop 1))
显然,未使用的递归定义的存在不应影响这是否会产生堆栈溢出,但它确实会产生堆栈溢出。您使用的是哪个.NET版本?我发现这些文章讨论了尾部调用优化vs,也许它们可以帮到忙。还有,您使用的是什么版本的F#?我使用F#4,在调试时,我根本看不到堆栈增长。我反汇编了IL代码,看起来F#4生成的代码不应该堆栈溢出。请包含完整的复制。调用代码是什么样子的?序列表达式中的“尾部递归”可能会令人困惑,因为序列实际上不会自行推进,而其他一些代码会。我已将此作为一个问题提交。是直觉推理还是非直觉推理导致了这个问题?我很想知道,这是否仅仅是使用
IL
inspection解决基本问题,并结合使用VS community edition将分解到我能做到的最小值,还是您使用了更多?我正在努力提高解决TCO问题的能力。如果你想把这作为一个单独的问题,那么我很乐意这样做。还有,你是从哪个版本的代码开始的,GitHub full,GitHub minimal,还是问题中的示例,因为我想独立地找出它。@GuyCoder-我的推理是这样的:在查看计算表达式时谈论“尾部调用”可能会产生误导-真的没有这样的事情(例如,参见一个相关讨论,尽管与序列表达式无关)。因此像gauss
这样的东西本身不会溢出堆栈,只有调用它的代码才能使堆栈溢出。@GuyCoder-但有一次我看到调用的代码就像Seq.nth
,这意味着几乎肯定存在编译器错误,因为“tail模式会产生!
ing”在序列中,表达式应该得到优化(不确定这是否是规范的一部分,但我认为这是众所周知的)。因此,这只是一个看看初始复制的哪些部分是必要的案例。用非递归定义替换原始代码中的循环
,使复制停止失败,删除模式匹配也是如此。@GuyCoder-我觉得查看IL没有帮助(在序列表达式的编译过程中涉及到很多编译器生成的机器),我只是尝试在源代码级别最小化复制,并对行为进行经验测试。