Array#push会导致大型数组出现“堆栈级别太深”错误

时间:2018-08-23 23:17:49

标签: arrays ruby

我制作了两个数组,每个数组包含一百万个项目:

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不是一个好主意吗?

2 个答案:

答案 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最有可能拔得头筹,但在那方面我可能会弄错。