我制作了两个数组,每个数组包含一百万个项目:
a1 = 1_000_000.times.to_a
a2 = a1.clone
我试图将a2推入a1:
a1.push *a2
这将返回SystemStackError: stack level too deep
。
但是,当我尝试使用concat
时,我没有收到错误消息:
a1.concat a2
a1.length # => 2_000_000
我也没有得到splat运算符的错误:
a3 = [*a1, *a2]
a3.length # => 2_000_000
为什么会这样?我查看了Array#push
的文档,它是用C语言编写的。我怀疑它可能在后台进行了递归操作,这就是为什么它会导致大型数组出现此错误的原因。它是否正确?对大型数组使用push
不是一个好主意吗?
答案 0 :(得分:25)
我认为这不是递归错误,而是参数堆栈错误。您将面临参数的Ruby VM堆栈深度限制。
问题是splat运算符,它作为参数传递给push
。 splat运算符被扩展为push
的一百万个元素参数列表。
作为函数参数作为堆栈元素传递,并且Ruby VM堆栈大小的预配置最大大小为:
RubyVM::DEFAULT_PARAMS[:thread_vm_stack_size]
=> 1048576
..这是限制的来源。
您可以尝试以下操作:
RUBY_THREAD_VM_STACK_SIZE=10000000 ruby array_script.rb
..它将正常工作。
这也是您要使用concat
的原因,因为整个数组可以作为一个引用传递,然后concat
将在内部处理该数组。与push
+ splat相对,后者将尝试将堆栈用作所有数组元素的临时存储。
答案 1 :(得分:2)
Casper已经回答了标题中的问题,并为您提供了可以使a1.push *a2
工作的解决方案,但是我想谈一谈您问的最后一个问题,这是否是一个好主意。
更具体地说,如果要使用生产代码中包含数百万个项目的数组,那么性能就成为要牢记的事情。 http://www.continuousthinking.com/2011/09/07/ruby_array_plus_vs_push.html概述了4种不同的方法来处理红宝石中的数组连接:+
,.push
,<<
和.concat
。
他们提到array.push
将有效地分别处理每个参数,并在每次数组太小时增加数组大小50%。这意味着在您的示例中,a
的大小将增加2倍,并获得100万个追加。同时,array.concat
将首先计算数组的新大小,扩展原始数组,然后将新数组复制到正确的位置。
对于像您这样的情况,无论是从内存还是从CPU使用率的角度来看,concat
的性能都可能更高。但是,没有基准我无法确定。我的建议是测量要处理的阵列大小所需的时间和内存使用率。 concat
最有可能拔得头筹,但在那方面我可能会弄错。