加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 综合聚焦 > 服务器 > 安全 > 正文

scala – 使方法实际内联

发布时间:2020-12-16 09:53:56 所属栏目:安全 来源:网络整理
导读:我伪造了一个简单的例子来检查@inline注释行为: import scala.annotation.tailrecobject 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)
我伪造了一个简单的例子来检查@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.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读