我正在编写一个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)
作为数组的setter的成员方法
我需要调用成员方法的原因是我需要在将项插入数组之前执行一些检查.
除了理解问题的根源之外,我想知道F#4.0是否暗示即将发布的FSharp.Compiler.Services将解决这个问题.鉴于更新的FSharp.Compiler.Services相对迫在眉睫,因此最好等待.
我在你的GitHub 问题上发布了这个,但在这里交叉发布,这样更容易找到:
我遇到过这样的情况:相互递归函数为 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
Run Code Online (Sandbox Code Playgroud)
.NET 4.6 和 F# 4.0 根本没有帮助。
我尝试将其重写为方法,但得到了 StackOverflowException。但是,我不明白为什么当我在没有尾部调用生成的情况下运行大量迭代时没有得到这样的结果?
更新 将方法重写为:
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
Run Code Online (Sandbox Code Playgroud)
将 JIT_TailCall 和 JIT_TailCallHelperStub_ReturnAddress 开销减少到 18%,执行时间缩短 2%,速度提高了 2 倍,因此实际开销从初始时间的 45% 减少到 10%。仍然很高,但并不像第一种情况那么惨淡。