我试图理解为什么在数组中推送许多(在我的情况下为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
被提出的原因。
答案 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_THREAD_VM_STACK_SIZE:
$ RUBY_THREAD_VM_STACK_SIZE=2097152 irb
> [].push(*Array.new(150808, 1)); nil
=> nil
这会增加你可以传递的参数数量。但是,这也意味着每个线程将分配两倍的堆栈,这可能是不可取的。您还应该注意,Fibers有一个单独的堆栈分配设置,通常要小得多,因为Fibers的设计是轻量级和一次性的。
你很少需要在堆栈上传递那么多数据;通常,如果需要将大量数据传递给方法,则应将对象作为参数传递(即,在堆栈上,例如哈希或数组),其存储在堆上分配,因此您的堆栈即使您的堆使用量以兆字节为单位,也会以字节为单位测量使用情况。也就是说,您可以将非常大的数组传递给您的方法(可以在堆上保存数十亿字节数而不会出现问题),然后您将在方法中迭代该数组。