使方法实际内联

ayv*_*ngo 6 annotations scala inline

我伪造了一个简单的例子来检查@inline注释行为:

import scala.annotation.tailrec

object InlineTest extends App {
  @inline
  private def corec(x : Int) : Int = rec(x - 1)

  @tailrec
  private def rec(x : Int) : Int =
    if (x < 3) x else {
      if (x % 3 == 0)
        corec(x-1)
      else
        rec(x-4)
    }

  @tailrec
  private def rec1(x : Int) : Int =
    if (x < 3) x else {
      if (x % 3 == 0) {
        val arg = x - 1
        rec1(arg - 1)
      } else
        rec1(x-4)
    }

  Console.println( rec(args(0).toInt) )
}
Run Code Online (Sandbox Code Playgroud)

这个例子在没有警告的情况下编译,两个tailrec注释都按照我的想法生效.但是当我实际执行代码时,它给了我stackoverflow异常.这意味着由于内联方法没有内联,尾递归优化失败.

我的控制功能rec1与原版不同,只能手动执行"内联"转换.并且因为此函数可以正常工作,避免使用尾递归的stackoverflow异常.

是什么阻止了带注释的方法被内联?

sjr*_*jrd 8

事实上,这是行不通的.治疗@inline方法比尾部调用的处理方式更晚,阻止了您希望的优化.

在编译器的所谓"tailcalls"阶段,编译器尝试转换方法,以便删除尾递归调用并用循环替换.请注意,只有同一功能中的调用才能以这种方式消除.所以,在这里,编译器将能够将函数rec转换为或多或少等效于以下内容:

  @tailrec
  private def rec(x0: Int) : Int =
    var x: Int = x0
    while (true) {
      if (x < 3) x else {
        if (x % 3 == 0)
          return corec(x-1)
        else {
          x = x-4
          continue
        }
      }
    }
Run Code Online (Sandbox Code Playgroud)

请注意,调用corec不会被消除,因为您正在调用另一种方法.那时在编译器中,@inline甚至没有注意注释.

但是为什么编译器不警告你它不能消除呼叫,因为你已经放了@tailrec?因为它只检查它实际上是否能够替换当前方法的所有调用.它不关心程序中的另一个方法是否调用rec(即使该方法,在这种情况下corec,本身也被调用rec).

所以你没有得到任何警告,但仍然被叫到的corec不是尾声消除.

当您@inline在编译器管道中稍后进行处理时,并假设它确实选择按照您的建议进行内联corec,它将再次修改以内rec联主体corec,这给出了以下内容:

  @tailrec
  private def rec(x0: Int) : Int =
    var x: Int = x0
    while (true) {
      if (x < 3) x else {
        if (x % 3 == 0)
          return rec((x-1) - 1)
        else {
          x = x-4
          continue
        }
      }
    }
Run Code Online (Sandbox Code Playgroud)

没关系,这会创建一个新的尾递归调用.太晚了.它不会再被淘汰了.因此,堆栈溢出.

您可以使用该TailCalls对象及其方法将相互尾递归方法转换为循环.请参阅API中的详细信息.