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异常.
是什么阻止了带注释的方法被内联?
事实上,这是行不通的.治疗@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中的详细信息.