我正在编写64位F#解决方案,并且分析显示出令人惊讶的&意外地在JIT_TailCall
中花费了大量时间......实际上它在运行时占主导地位(大约80%)。这与其邪恶的堂兄JIT_TailCallHelperStub_ReturnAddress
一起出现。
我肯定已经跟踪了在跨程序集边界的方法或属性调用中传递struct
类型(自定义值类型)的源代码。我确信这是因为如果我绕过方法调用并直接将struct
分配给属性(违规方法正在使用的那个),性能会神奇地提高4-5倍,运行时间减少!
调用程序集正在使用F#3.1,因为它是使用FSharp.Compiler.Services的最新稳定版本动态编译的。
正在调用的程序集使用的是F#4.0 / .NET 4.6(VS 2015)。
简化我要做的是从动态生成的程序集中为数组中的位置指定自定义struct
值...
运行时很快,调用时不会产生无关的尾调用:
但是,由于在调用时生成无关的尾调用,运行时很慢:
公开数组的索引器属性(Item)
充当数组
我需要调用成员方法的原因是我需要在将项目插入数组之前执行一些检查。
除了理解问题的根源之外,我想知道F#4.0是否暗示即将发布的FSharp.Compiler.Services会解决这个问题。鉴于更新后的FSharp.Compiler.Services相对迫近,最好等待。
答案 0 :(得分:1)
我是在GitHub question上发布的,但在此处交叉发布,以便更容易找到:
我有一个案例,相互递归函数为JIT_TailCall生成30%的负载,为JIT_TailCallHelperStub_ReturnAddress生成15%的负载。这些函数在方法变量和类字段上关闭。当我关闭尾部呼叫时,我的性能提高了45%。
我还没有对此片段进行过分析,但这就是我的真实代码的结构:
#time "on"
type MyRecType() =
let list = System.Collections.Generic.List()
member this.DoWork() =
let mutable tcs = (System.Runtime.CompilerServices.AsyncTaskMethodBuilder<int>.Create())
let returnTask = tcs.Task // NB! must access this property first
let mutable local = 1
let rec outerLoop() =
if local < 1000000 then
innerLoop(1)
else
tcs.SetResult(local)
()
and innerLoop(inc:int) =
if local % 2 = 0 then
local <- local + inc
outerLoop()
else
list.Add(local) // just fake access to a field to illustrate the pattern
local <- local + 1
innerLoop(inc)
outerLoop()
returnTask
let instance = MyRecType()
instance.DoWork().Result
> Real: 00:00:00.019, CPU: 00:00:00.031, GC gen0: 0, gen1: 0, gen2: 0
> val it : int = 1000001
.NET 4.6和F#4.0完全没有帮助。
我试图将其重写为方法,但得到了StackOverflowException。但是,我不明白为什么在没有尾调用的情况下运行大量迭代时我没有得到SO?
<强>更新强> 将方法重写为:
member this.DoWork2() =
let mutable tcs = (System.Runtime.CompilerServices.AsyncTaskMethodBuilder<int>.Create())
let returnTask = tcs.Task // NB! must access this property first
let mutable local = 1
let rec loop(isOuter:bool, inc:int) =
if isOuter then
if local < 1000000 then
loop(false,1)
else
tcs.SetResult(local)
()
else
if local % 2 = 0 then
local <- local + inc
loop(true,1)
else
list.Add(local) // just fake access to a field to illustrate the pattern
local <- local + 1
loop(false,1)
loop(true,1)
returnTask
> Real: 00:00:00.004, CPU: 00:00:00.015, GC gen0: 0, gen1: 0, gen2: 0
> val it : int = 1000001
将JIT_TailCall和JIT_TailCallHelperStub_ReturnAddress开销减少到18%,执行时间缩短2%,快2倍,因此实际开销从初始时间的45%减少到10%。仍然很高,但并不像第一种情况那样令人沮丧。