【问题标题】:Scala recursion depth differences and StackOverflowError below allowed depthScala递归深度差异和低于允许深度的StackOverflowError
【发布时间】:2016-02-28 17:59:52
【问题描述】:

编写以下程序是为了检查 Scala 中的深度递归在我的机器上的深度。我认为它主要取决于分配给该程序的堆栈大小。然而,在计算出最大递归深度(在出现异常时捕获异常)并尝试模拟此深度的递归后,我得到了奇怪的输出。

object RecursionTest {

  def sumArrayRec(elems: Array[Int]) = {

    def goFrom(i: Int, size: Int, elms: Array[Int]): Int = {
      if (i < size) elms(i) + goFrom(i + 1, size, elms)
      else 0
    }
    goFrom(0, elems.length, elems)
  }

  def main(args: Array[String]) {
    val recDepth = recurseTest(0, 0, Array(1))
    println("sumArrayRec = " + sumArrayRec((0 until recDepth).toArray))
  }

  def recurseTest(i: Int, j: Int, arr: Array[Int]): Int = {
    try {
      recurseTest(i + 1, j + 1, arr)
    } catch { case e: java.lang.StackOverflowError =>
      println("Recursion depth on this system is " + i + ".")
      i
    }
  }

}

结果因程序的执行而异。

其中一个是理想的输出:

/usr/lib/jvm/java-8-oracle/bin/java

Recursion depth on this system is 7166.
sumArrayRec = 25672195

Process finished with exit code 0

尽管如此,我得到的第二个可能的输出表明一个错误:

/usr/lib/jvm/java-8-oracle/bin/java

Recursion depth on this system is 8129.
Exception in thread "main" java.lang.StackOverflowError
    at RecursionTest$.goFrom$1(lab44.scala:6)
    at RecursionTest$.goFrom$1(lab44.scala:6)
    at RecursionTest$.goFrom$1(lab44.scala:6)
    (...)
    at RecursionTest$.goFrom$1(lab44.scala:6)
    at RecursionTest$.goFrom$1(lab44.scala:6)

Process finished with exit code 1

我没有观察到它们之间的任何依赖或关系,我只是得到了第一个,其他时候得到了第二个

所有这些都让我想到了以下问题:

  1. 为什么会出现错误?是什么原因导致的?
  2. 这是堆栈溢出错误吗,如果是...
  3. 为什么程序运行时栈的大小会同时变化?有可能吗?

当我将println("sumArrayRec = " + sumArrayRec((0 until recDepth).toArray)) 更改为 println("sumArrayRec = " + sumArrayRec((0 until recDepth - 5000).toArray)),更不用说,行为保持不变。

【问题讨论】:

    标签: scala recursion jvm stack-overflow


    【解决方案1】:
    1. 您会导致堆栈溢出,因为您导致堆栈溢出
    2. 是的
    3. 最大堆栈不变,JVM 设置它。您的两种方法recurseTestsumArrayRec 将不同的数量推入堆栈。 recurseTest基本上在每次调用时都会在堆栈中添加 2 个整数,而 sumArrayRec 会添加 3 个整数,因为您调用了 elms(i),可能更多的是 add/ifelse(更多说明)。很难说清楚,因为 JVM 正在处理所有这些,它的目的是从您需要知道的内容中掩盖这一点。此外,谁知道 JVM 在幕后做了哪些优化,这会极大地影响每个方法调用创建的堆栈大小。在多次运行中,由于系统时序等原因,您将获得不同的堆栈深度,也许 jvm 会在程序运行的短时间内优化,也许不会,在这种情况下它是不确定的。如果您事先运行了一些预热代码,这可能会使您的测试更具确定性,因此任何优化都会发生或不会发生。

    您应该考虑使用 JMH 之类的东西进行测试,这将有助于确定性。

    附注:您也可以使用 -Xss2M 手动更改堆栈大小,这对于 SBT 使用很常见。

    【讨论】:

    • 你为什么说recurseTest只推两个参数,因为它接收三个参数?
    • 因此它创建了 2 个新整数,将 2 个整数发送给它,以及一个指针,因此堆栈上大约有 5 个整数(不包括指令),数组可能会因为未使用而被优化掉。除非您查看生成的程序集-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly,否则您真的无法判断,但 JVM 优化可能会在运行时改变这些
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-02-20
    • 1970-01-01
    • 2021-10-28
    • 2015-02-28
    • 2010-10-25
    • 2011-05-29
    相关资源
    最近更新 更多