将超过130798个对象推入数组时出现SystemStackError

时间:2015-02-24 18:34:30

标签: ruby

我试图理解为什么在数组中推送许多(在我的情况下为130798)对象会返回SystemStackError

big = Array.new(130797, 1)
[].push(*big) && false
=> false

bigger = Array.new(130798, 1)
[].push(*bigger) && false
=> SystemStackError: stack level too deep
     from (irb):104
     from /Users/julien/.rbenv/versions/2.2.0/bin/irb:11:in `<main>'

我能够在MRI 1.9.3和2.2.0上重现它,而Rubinius(2.5.2)没有出现错误。

我理解这是由于Array在MRI中实施的方式,但并不完全理解SystemStackError被提出的原因。

1 个答案:

答案 0 :(得分:9)

Ruby的错误消息(“堆栈级别太深”)在这里不准确 - Ruby真正说的是“我用尽了堆栈内存”,这是通常由无限递归引起的,但在这种情况下,是由于您传递的参数多于Ruby已分配的内存来处理。

Ruby 2.0+的最大堆栈大小由RUBY_THREAD_VM_STACK_SIZE控制(在2.0之前,这由C限制控制,通过ulimit设置)。传递给方法的每个参数都被推送到线程的堆栈中;如果你将更多的参数推送到堆栈上,而RUBY_THREAD_VM_STACK_SIZE有足够的空间容纳,你将得到一个SystemStackError。你可以从IRB看到这个限制:

RubyVM::DEFAULT_PARAMS[:thread_vm_stack_size]
=> 1048576

默认情况下,每个线程都有1MB的堆栈可供使用。 Ruby Fixnum大8字节,在我的系统上,我溢出130808个参数,或者分配1046464个字节,剩下2112个字节分配给其余的调用堆栈。通过使用splat运算符(*),你会说“获取130798个Fixnums的列表,并将其扩展为130798个参数,以便在堆栈中传递”;你只是没有足够的堆栈内存来保存它们。

如果需要,可以在调用Ruby时增加RUBY_T​​HREAD_VM_STACK_SIZE:

$ RUBY_THREAD_VM_STACK_SIZE=2097152 irb
> [].push(*Array.new(150808, 1)); nil
 => nil

这会增加你可以传递的参数数量。但是,这也意味着每个线程将分配两倍的堆栈,这可能是不可取的。您还应该注意,Fibers有一个单独的堆栈分配设置,通常要小得多,因为Fibers的设计是轻量级和一次性的。

你很少需要在堆栈上传递那么多数据;通常,如果需要将大量数据传递给方法,则应将对象作为参数传递(即,在堆栈上,例如哈希或数组),其存储在堆上分配,因此您的堆栈即使您的堆使用量以兆字节为单位,也会以字节为单位测量使用情况。也就是说,您可以将非常大的数组传递给您的方法(可以在堆上保存数十亿字节数而不会出现问题),然后您将在方法中迭代该数组。