Ruby数组concat与+;速度

Ruby数组concat与+;速度,ruby,arrays,performance,Ruby,Arrays,Performance,我对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 pu

我对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 
根据调查,区别在于:

数组#+

Concatenation-返回通过将两个数组连接在一起生成第三个数组而生成的新数组

数组#concat

数组#concat:将其他数组的元素附加到self


因此,
+
操作符每次被调用时都会创建一个新数组(这很昂贵),而
concat
只会添加新元素。

答案在于Ruby的
+
操作符和
concat
方法的底层C实现

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;
}
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;
}

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;
}
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方法只是将新数组拼接到原始数组中。

如果要运行基准测试,请利用预构建的工具,并将测试减少到测试所需的最低限度

首先,它为其基准测试提供了很多智能:

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的内置类可以帮助:

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
,它对原始数组具有破坏性(变异),另一种是非破坏性的(
+
),它是纯功能的,没有变异

我来这里是为了寻找一个更具可比性的测试,当时我没有意识到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)

我使用两个版本的ruby进行了基准测试。结果表明,concat比plus(+)更快

ruby-2.5.3

       user     system      total        real
concat  1.347328   0.001125   1.348453 (  1.349277)
plus  1.405046   0.000110   1.405156 (  1.405682)
       user     system      total        real
concat  1.263601   0.012012   1.275613 (  1.276105)
plus  1.336407   0.000051   1.336458 (  1.336951)
       user     system      total        real
concat  1.264517   0.019985   1.284502 (  1.285004)
plus  1.329239   0.000002   1.329241 (  1.329733)
       user     system      total        real
concat  1.347648   0.004012   1.351660 (  1.352149)
plus  1.821616   0.000034   1.821650 (  1.822307)
       user     system      total        real
concat  1.256387   0.000000   1.256387 (  1.256828)
plus  1.269306   0.007997   1.277303 (  1.277754)
ruby-2.7.1

       user     system      total        real
concat  1.406091   0.000476   1.406567 (  1.406721)
plus  1.295966   0.000044   1.296010 (  1.296153)
       user     system      total        real
concat  1.281295   0.000000   1.281295 (  1.281436)
plus  1.267036   0.000027   1.267063 (  1.267197)
       user     system      total        real
concat  1.291685   0.000003   1.291688 (  1.291826)
plus  1.266182   0.000000   1.266182 (  1.266320)
       user     system      total        real
concat  1.272261   0.000001   1.272262 (  1.272394)
plus  1.265784   0.000000   1.265784 (  1.265916)
       user     system      total        real
concat  1.272507   0.000001   1.272508 (  1.272646)
plus  1.294839   0.000000   1.294839 (  1.294974)
内存使用

require "benchmark/memory"

N = 10_000_00
Benchmark.memory do |x|
  x.report("array concat") { N.times { [].concat([4,5]) } }
  x.report("array +") { N.times { [] + [4, 5] } }

  x.compare!
end

Calculating -------------------------------------
        array concat    80.000M memsize (     0.000  retained)
                         2.000M objects (     0.000  retained)
                         0.000  strings (     0.000  retained)
             array +   120.000M memsize (     0.000  retained)
                         3.000M objects (     0.000  retained)
                         0.000  strings (     0.000  retained)

Comparison:
        array concat:   80000000 allocated
             array +:  120000000 allocated - 1.50x more

仅供参考:)关于+=?从技术上讲,它与#concat相同吗?@NoICE在Ruby中,可以用几种方法编写操作数表达式。这里最相关的是
x=x+y
,它相当于
x+=y
。除非某个类专门覆盖加号运算符以委托给
concat
+=
不会执行与
concat
完全相同的操作,因为它使用的是
rb\u-ary\u plus
而不是
rb\u-ary\u-concat
。仅包括所选输入的实际性能时间是相对误导性的,因为它是一个非常小的n值,会产生非常“温和”的差异。(最初的测试实际上通过“意外的副作用”更好地显示了性能差异。)@YuriiVerbytskyi我回答这个问题的原因是因为Tinman给出的答案是6年前的。所以,我想我可以对ruby的最新版本进行更新比较。如果有问题,我可以删除我的答案。这不是问题,但是指出这个结果的原因而不仅仅是新的基准会更有用。主要的是di通过此操作创建的不同数量的对象(在
plus
案例中创建的对象数量增加了1.5倍):
.times{[].concat([4,5])
-2个创建的对象([]和[4,5]),首先返回的是
times{[]+[4,5]}
这里是每次([],[4,5]和
[[]+[4,5]返回的第三个对象
new object creating every iteration)您可以尝试添加一些关于内存分配的有用信息,例如基于这个gem@YuriiVerbytskyi,我已经用与内存分配相关的信息更新了答案