我在scala中写过一些性能关键的代码,并且时不时地遇到如下代码:
var index = start - 1
while {index += 1; index < end} do something
然后我想写一个内联版本以避免再次出现
var index
和while { .. }
:
inline def forward(start: Int, end: Int)(inline body: Int => Unit): Unit =
if start < end then
body(start)
forward(start + 1, end)(body)
我认为
forward(start, end){ something }
会生成与手写while {}
几乎相同的代码。
但是我遇到了一条有线的编译消息让我很困惑:
object InlineFor:
val n: Int = 1
inline def forward(start: Int, end: Int)(inline body: Int => Unit): Unit =
if start < end then
body(start)
forward(start + 1, end)(body)
def main(args: Array[String]): Unit =
forward(0, 1)(println) // This is OK
forward(0, n)(println) // This is failed
超出了连续内联的最大数量(32),也许这是 由递归内联方法引起?您可以使用 -Xmax-inlines 来 改变限制。
代码有什么问题吗? 3x 非常多。
错误消息说明了一切:您有一个内联递归方法。
这仅支持已知的迭代次数(在编译时)以及由编译器标志定义的最大次数
-Xmax-inlines
。
编译器无法在不知道代码中的
n
的情况下内联代码。
请注意,您的代码中根本不需要
inline
,只需用@tailrec
注释您的方法即可强制它是尾递归,编译器将生成优化的代码。
如果将 n 的类型更改为
1
,则可以解决此问题:
object InlineFor:
val n: 1 = 1
inline def forward(start: Int, end: Int)(inline body: Int => Unit): Unit =
if start < end then
body(start)
forward(start + 1, end)(body)
def main(args: Array[String]): Unit =
forward(0, n)(println) // ok again
原因是编译器需要知道类型级别
上
start
和
end
的值才能正确确定 if start < end
。
但是:我不认为内联递归是您正在寻找的。如果您调用
forward(0, 32)(println)
,您将再次遇到相同的编译器错误,因为这样您就真的达到了递归内联的默认限制(即 32)。
您应该意识到(大大简化)对内联函数的每次调用都将导致该调用被函数体替换。因此,生成的字节码将包含
body
字节码的 n 个实例,而不是循环。 (这就是递归内联调用受到限制的原因。)
如果您使用 Intellij IDEA:“查看”菜单下有一个“显示字节码”选项。我建议您使用它,并观察如果您增加
n
的值,您的字节码将如何增长。
不要使用递归,因为你无法阻止递归调用被内联。将
while
与 var
一起使用 - 这个 var
将是本地的,隐藏在您的 InlineFor
对象中。这被认为是可以接受的,这就是实现了多少标准库函数。
inline def forward(start: Int, end: Int)(inline body: Int => Unit): Unit =
var i = start
while i < end do
body(i)
i = i + 1