如何增加 Ruby 应用程序的堆栈大小。递归应用程序出现“堆栈级别太深(SystemStackError)”错误。

39

在stackoverflow.com上发布一个堆栈溢出的问题,多有趣啊:-)

我在运行一些递归Ruby代码时,遇到了:"Stack level too deep (SystemStackError)"

(我相当确定代码是可以工作的,我没有陷入无限递归死循环,但这并不是重点)

有没有办法改变我的Ruby应用程序允许的堆栈深度/大小?

我不太明白这是否是Ruby中的限制,因为错误显示“栈级别”,这让我觉得Ruby会以某种方式计算堆栈的“级别”,或者它只是意味着堆栈已满。

我尝试在Vista和Ubuntu下运行此程序,结果相同。在Ubuntu下,我尝试使用'ulimit -s'将堆栈大小从8192更改为16000,但没有任何改变。

编辑:感谢反馈。
我确实意识到使用递归函数可能不是最稳健的方法。但那也不是重点。我只是想知道是否有一种方法可以增加堆栈大小..只要。正如我之前提到过的,我尝试在运行ruby脚本之前运行ulimit -s 16000..但没有任何改进..我用错了吗?

编辑2:实际上,在代码的边缘情况下,我正在进行无限递归。
当您获得"Stack level too deep"错误时截断的Ruby堆栈跟踪有点误导人。
当涉及到几个函数的递归行为时,您会感到递归次数比实际少得多。在这个例子中,你可能认为它在稍微多一点的调用后崩溃了,但实际上是大约15000次调用。

tst.rb:8:in `p': stack level too deep (SystemStackError)
        from tst.rb:8:in `bar'
        from tst.rb:12:in `bar'
        from tst.rb:19:in `foo'
        from tst.rb:10:in `bar'
        from tst.rb:19:in `foo'
        from tst.rb:10:in `bar'
        from tst.rb:19:in `foo'
        from tst.rb:10:in `bar'
         ... 190 levels...
        from tst.rb:19:in `foo'
        from tst.rb:10:in `bar'
        from tst.rb:19:in `foo'
        from tst.rb:22

-安德烈亚斯

7个回答

29

这个问题和它的回答似乎可以追溯到使用C栈的Ruby 1.8.x。Ruby 1.9.x及以后版本使用具有自己堆栈的虚拟机。在Ruby 2.0.0及以后版本,可以通过RUBY_THREAD_VM_STACK_SIZE环境变量控制VM堆栈的大小。


6
有用的!例如,export RUBY_THREAD_VM_STACK_SIZE=5000000 将其设置为5 MB。 - Jochem Schulenklopper

13

如果你确信没有无限递归的情况,那么你的算法可能不适合在Ruby中以递归方式执行。将算法从递归转换为其他类型的堆栈相当容易,我建议你尝试一下。以下是如何实现:

def recursive(params)
  if some_conditions(params)
     recursive(update_params(params))
  end
end

recursive(starting_params)

将会转换为

stack = [starting_params]
while !stack.empty?
  current_params = stack.delete_at(0)
  if some_conditions(current_params)
    stack << update_params(current_params)
  end
end

8

Yukihiro Matsumoto在这里写道:

Ruby使用C堆栈,因此您需要使用ulimit来指定堆栈深度上限。


7

Ruby使用C堆栈,因此您的选项包括使用ulimit或使用某些编译器/链接器堆栈大小标志来编译Ruby。尾递归尚未实现,Ruby当前对递归的支持也不太好。虽然递归很酷,很优雅,但你可能需要考虑应对语言的限制,并以不同的方式编写代码。


8
这个答案适用于1.9版本之前的Ruby。对于1.9版本或更高版本,请参见https://dev59.com/VHVC5IYBdhLWcg3wnCWA#27510458。 - Wayne Conrad

3

考虑一下代码的情况。正如其他发帖者提到的,可以黑掉解释器的C代码。然而,结果将是您使用更多的RAM,并且不能保证不会再次发生堆栈溢出。

真正好的解决方案是为您尝试做的事情设计一个迭代算法。有时候记忆化可能有所帮助,有时候你会发现你压入堆栈的东西并没有用到,这种情况下您可以用可变状态替换递归调用。

如果您对此类内容不熟悉,请查看SICP这里以获取一些想法......


3

我也遇到了同样的问题,但在Linux或Mac上很容易解决。就像其他答案中提到的那样,Ruby使用系统堆栈设置。你可以通过设置堆栈大小来轻松更改Mac和Linux上的该设置。例如:

ulimit -s 20000

2

Ruby 1.9.2版本开始,您可以使用以下方式开启尾调用优化:

RubyVM::InstructionSequence.compile_option = {
  tailcall_optimization: true,
  trace_instruction: false
}

RubyVM::InstructionSequence.new(<<-EOF).eval
  def me_myself_and_i
    me_myself_and_i
  end
EOF
me_myself_and_i # Infinite loop, not stack overflow

这将避免在递归调用位于方法结尾并且仅限于该方法时出现“SystemStackError”错误。当然,这个例子会导致无限循环。最好在进行深度递归之前使用浅递归(和不优化)进行调试。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接