2014-05-20 14 views
20

我做了Ruby的陣列concat() vs +操作的小性能測試,concat()太快了。Ruby Array 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 
+6

僅供參考:) http://www.ruby-doc.org/stdlib-1.9.3/libdoc/benchmark/rdoc /Benchmark.html –

+0

http://www.joelonsoftware.com/articles/fog0000000319.html – Fuser97381

回答

34

按照Ruby docs,所不同的是:

陣列#+

級聯 - 返回建立了一個新的數組通過將兩個陣列連接在一起以產生第三個陣列。

陣列#的concat

陣列#的concat:追加的other_ary自營元件。

所以+操作者將創建一個新的陣列中的每個被調用時(這是昂貴的),而僅concat追加新元素。

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方法只是將新陣列拼接成原始陣列。

+0

完美。謝謝! –

+0

+ =呢?技術上它和#concat一樣嗎? –

+1

@NoICE在Ruby中,可以用幾種方法編寫操作數表達式。這裏最相關的是'x = x + y',它相當於'x + = y'。除非一個類專門覆蓋加號操作符來委託給'concat',否則'+ ='不會像'concat'一樣執行,因爲它使用'rb_ary_plus'而不是'rb_ary_concat'。 –

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 

當一切都足夠接近不是真的擔心,果味會告訴我們,他們是「相似」。

在這一點上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) 

通知的時間不同。運行一次測試可能會導致誤導性結果,因此可以多次運行它們。此外,請確保您的循環產生的時間不會掩埋在由進程開始引起的背景噪音中。

+0

僅包含選擇輸入的實際執行時間包含相對誤導性,因爲它是一個非常小的n值,可產生非常「馴服」的差異。 – user2864740

+0

(原始測試實際上通過'意想不到的副作用'更好地顯示性能差異。) – user2864740

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)