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

jgo*_*yon 4 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>'
Run Code Online (Sandbox Code Playgroud)

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

我知道这是由于Array在MRI中实施的方式,但不太明白为什么a SystemStackError被提出.

Chr*_*ald 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
Run Code Online (Sandbox Code Playgroud)

默认情况下,每个线程都有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
Run Code Online (Sandbox Code Playgroud)

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

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