Ruby Array concat vs + speed?

我做了Ruby的数组concat() vs +操作的小性能测试, concat()太快了。

但是我不清楚为什么concat()这么快?

有人可以帮忙吗?

这是我使用的代码:

 t = Time.now ar = [] for i in 1..10000 ar = ar + [4,5] end puts "Time for + " + (Time.now - t).to_s t = Time.now ar = [] for i in 1..10000 ar.concat([4,5]) end puts "Time for concat " + (Time.now - t).to_s 

根据Ruby文档 ,区别在于:

数组#+

连接 – 返回通过将两个数组连接在一起以生成第三个数组而构建的新数组。

数组#concat

Array#concat:将other_ary的元素追加到self。

因此, +运算符将在每次调用时创建一个新数组(这很昂贵),而concat只会附加新元素。

答案在于Ruby的+运算符和concat方法的底层C实现。

Array#+

 rb_ary_plus(VALUE x, VALUE y) { VALUE z; long len, xlen, ylen; y = to_ary(y); xlen = RARRAY_LEN(x); ylen = RARRAY_LEN(y); len = xlen + ylen; z = rb_ary_new2(len); ary_memcpy(z, 0, xlen, RARRAY_CONST_PTR(x)); ary_memcpy(z, xlen, ylen, RARRAY_CONST_PTR(y)); ARY_SET_LEN(z, len); return z; } 

Array#concat

 rb_ary_concat(VALUE x, VALUE y) { rb_ary_modify_check(x); y = to_ary(y); if (RARRAY_LEN(y) > 0) { rb_ary_splice(x, RARRAY_LEN(x), 0, y); } return x; } 

如您所见, +运算符正在从每个数组复制内存,然后创建并返回包含两者内容的第三个数组。 concat方法只是将新数组拼接成原始数组。

如果您要运行基准测试,请利用预构建工具并将测试降低到测试您想要知道的最低要求。

从Fruity开始,它为其基准测试提供了大量智能:

 require 'fruity' compare do plus { [] + [4, 5] } concat { [].concat([4, 5]) } end # >> Running each test 32768 times. Test will take about 1 second. # >> plus is similar to concat 

当事情足够接近而不是真的担心时,Fruity会告诉我们他们“相似”。

那时Ruby的内置Benchmark类可以帮助:

 require 'benchmark' N = 10_000_000 3.times do Benchmark.bm do |b| b.report('plus') { N.times { [] + [4, 5] }} b.report('concat') { N.times { [].concat([4,5]) }} end end # >> user system total real # >> plus 1.610000 0.000000 1.610000 ( 1.604636) # >> concat 1.660000 0.000000 1.660000 ( 1.668227) # >> user system total real # >> plus 1.600000 0.000000 1.600000 ( 1.598551) # >> concat 1.690000 0.000000 1.690000 ( 1.682336) # >> user system total real # >> plus 1.590000 0.000000 1.590000 ( 1.593757) # >> concat 1.680000 0.000000 1.680000 ( 1.684128) 

注意不同的时间。 一次运行测试可能会导致误导结果,因此请多次运行它们。 此外,请确保您的循环导致一个时间不会被过程开始引起的背景噪音所掩盖。

正如其他答案所述,OP的问题是比较两个执行不同目的的运营商。 一, concat ,它破坏(变异)原始数组, +是非破坏性的(纯function,无突变)。

我来到这里寻找一个更具可比性的测试,当时没有意识到,concat是破坏性的。 如果它对于想要比较两个纯函数,非破坏性操作的其他人有用,这里是数组加法( array1 + array2 )与数组扩展( [*array1, *array2] )的基准。 据我所知,两者都会导致创建3个数组:2个输入数组,1个新结果数组。

提示: +胜利。

 # a1 is a function producing a random array to avoid caching a1 = ->(){ [rand(10)] } a2 = [1,2,3] n = 10_000_000 Benchmark.bm do |b| b.report('expand'){ n.times{ [*a1[], *a2] } } b.report('add'){ n.times{ a1[]+a2 } } end 

结果

 user system total real expand 9.970000 0.170000 10.140000 ( 10.151718) add 7.760000 0.020000 7.780000 ( 7.792146)