2009年7月22日水曜日

[Ruby] inject vs each

なんらかの初期値があり、それに配列(やコレクション)のすべての要素を順に適用していくような場合、for や each よりも inject のほうがスマートに書けます。(初期値を外に出さなくていい、という意味で。)

パフォーマンス的にはどうなのだろう、と思って簡単なスクリプトで測ってみました。
1000000個の要素(すべて 1 )をもつ配列の、全要素の和を求めるだけのスクリプトです(10回繰り返しているのは平均をとるため)。

(環境)


$ruby -v
ruby 1.8.7 (2009-04-08 patchlevel 160) [i686-linux]



for.rb

ary = Array.new(1000000,1)
t = 0
10.times{
time1 = Time.now

sum = 0
for i in 0..ary.size-1
sum += ary[i]
end

time2 = Time.now
t += time2 - time1
}

puts "for : (avg time) #{t / 10} ms"



each.rb

ary = Array.new(1000000,1)
t = 0
10.times{
time1 = Time.now

sum = 0
ary.each{ |elem|
sum += elem
}

time2 = Time.now
t += time2 - time1
}

puts "each : (avg time) #{t / 10} ms"


inject.rb

ary = Array.new(1000000,1)
t = 0
10.times{
time1 = Time.now

sum = ary.inject(0){ |result, elem|
result += elem
}

time2 = Time.now
t += time2 - time1
}

puts "inject : (avg time) #{t / 10} ms"


-- 実行結果 --


$ ruby for.rb
for : (avg time) 0.6509955 ms

$ ruby each.rb
each : (avg time) 0.5049966 ms

$ ruby inject.rb
inject : (avg time) 3.6189753 ms


eachが一番速いのは予想通りとして、測り方間違えた?と思うくらい inject が重い。


では、関数型言語だとどうだろう?と、Gauche (scheme) で書いてみました。こちらは for-each vs fold になります。(平均はとってません...。)


(環境)


$ gosh -V
Gauche scheme interpreter, version 0.8.14 [utf-8,pthreads]



make-list.scm(list作成用)

(define (make-list num)
(let loop ((lis '()) (n num))
(if (eq? 0 n)
lis
(loop (cons 1 lis) (- n 1)))))


for-each.scm

(define lis (make-list 1000000))
(define sum 0)
(time
(for-each (lambda (x) (set! sum (+ sum x))) lis))


fold.scm

(define lis (make-list 1000000))
(time (fold + 0 lis))


-- 実行結果 --


$ gosh for-each.scm
;(time (for-each (lambda (x) (set! sum (+ sum x))) lis))
; real 0.230
; user 0.230
; sys 0.000

$ gosh fold.scm
;(time (fold + 0 lis))
; real 0.620
; user 0.590
; sys 0.030


互角か?と思っていたのですが、Gauche でも for-each のほうが速いようです。


# まぁRubyでこんな巨大な配列を扱うことはないのかも

0 件のコメント:

コメントを投稿