使方法实际内联

时间:2014-08-30 12:45:44

标签: scala annotations 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) )
}

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

我的控制功能rec1与原版只有#34; inline&#34;手动进行转换。因为这个函数可以正常工作,避免了带尾递归的stackoverflow异常。

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

2 个答案:

答案 0 :(得分:7)

确实,这不会奏效。 @inline的处理方式晚于尾调用的处理方式,阻止了您希望的优化。

在所谓的&#34;尾随呼叫期间&#34;编译器的阶段,编译器尝试转换方法,以便删除尾递归调用并由循环替换。请注意,只有同一功能中的调用才能以这种方式消除。所以,在这里,编译器将能够将函数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
        }
      }
    }

请注意,对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
        }
      }
    }

不要小心,这会创建一个新的尾递归调用。太晚了。它不再被淘汰了。因此,堆栈溢出。

您可以使用TailCalls对象及其方法将相互尾递归方法转换为循环。请参阅details in the API

答案 1 :(得分:2)

请注意,正在进行的提案(scala PR 567)会引入实际的databaseRef.child("users").child(userID!).child("medals").observeSingleEventOfType(.Value, withBlock: { (snapshot) in // Get user medals self.identities3 = snapshot.value as! [String] self.collectionView!.reloadData() }) 关键字。 (2016年9月)

请参阅&#34; SIP NN: Inline Definitions and Meta Expressions (Public Draft)&#34;

它适用于:

  

具体的价值定义,例如

inline
  

具体方法,例如

inline val x = 4
  

内联方法的参数,例如

inline def square(x: Double) = x * x
  

内联标记的值和方法定义实际上是最终的;它们不能被覆盖。
  内联成员也永远不会覆盖其他成员。相反,每个内联成员都成为具有相同名称的所有其他成员的重载替代。

     

内联定义仅在编译时存在;在对象布局中没有为它们分配存储,并且在对象方法表中没有为它们生成代码   这意味着可以使内联成员具有与具有相同名称的其他成员相同类型的擦除。