如何消除JIT_TailCall中为真正非递归的函数花费的时间

时间:2015-07-15 14:44:42

标签: .net f#

我正在编写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值...

运行时很快,调用时不会产生无关的尾调用:

  1. 在类型
  2. 中公开私有数组的属性

    但是,由于在调用时生成无关的尾调用,运行时很慢:

    1. 公开数组的索引器属性(Item)

    2. 充当数组

    3. 的setter的成员方法

      我需要调用成员方法的原因是我需要在将项目插入数组之前执行一些检查。

      ,实用

      除了理解问题的根源之外,我想知道F#4.0是否暗示即将发布的FSharp.Compiler.Services会解决这个问题。鉴于更新后的FSharp.Compiler.Services相对迫近,最好等待。

1 个答案:

答案 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%。仍然很高,但并不像第一种情况那样令人沮丧。