我对Ruby的数组concat()
与+
操作进行了小型性能测试,而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
答案 0 :(得分:36)
根据Ruby docs,区别在于:
数组#+ :
Concatenation - 返回通过将两个数组连接在一起以生成第三个数组而构建的新数组。
数组#concat :
Array#concat:将other_ary的元素追加到self。
因此+
运算符将在每次调用时创建一个新数组(这很昂贵),而concat
只会附加新元素。
答案 1 :(得分:13)
答案在于Ruby的+
运算符的基础C实现和concat
方法。
<强> 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
方法只是将新数组拼接成原始数组。
答案 2 :(得分:7)
如果您要运行基准测试,请利用预建工具并将测试降低到测试您想要知道的最低限度。
从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会告诉我们他们&#34;类似&#34;。
此时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)
注意不同的时间。运行一次测试可能会导致误导结果,因此请多次运行它们。此外,请确保您的循环导致时间不会被开始的过程引起的背景噪音所掩盖。
答案 3 :(得分:2)
我使用两种版本的红宝石进行了基准测试。结果表明concat比plus(+)更快
require 'benchmark'
N = 10_000_000
5.times do
Benchmark.bm do |b|
b.report('concat') { N.times { [].concat([4,5]) }}
b.report('plus') { N.times { [] + [4, 5] }}
end
end
红宝石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)
红宝石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
答案 4 :(得分:1)
正如其他答案所述,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)