scala – 使方法实际内联
我伪造了一个简单的例子来检查@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,它与原版只有手动执行“内联”转换.并且因为此函数可以正常工作,避免使用尾递归的stackoverflow异常. 是什么阻止了带注释的方法被内联? 解决方法
事实上,这是行不通的. @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 } } } 请注意,不会消除对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. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |