Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

Rubyで配列の要素を合計するベストな方法

More than 1 year has passed since last update.

Ruby でベンチマークを取る方法を読んでいて、配列の合計値の計算方法っていろいろあるよなーと思ったので。

配列の要素を合計する方法

  1. 素直に一つずつeachでループして足していく
  2. Enumerable#injectにブロック(ないしはProcオブジェクト)を渡す
  3. Enumerable#injectにシンボルを渡す
  4. sumメソッド(Ruby2.4以上)
# 1
sum = 0
array.each { |i| sum += i }
# 2
# array.inject { |sum, i| sum + i } と等価
array.inject(&:+)
# 3
array.inject(:+)
# 4
array.sum

うーん、さすがTMTOWTDI

結局どれが一番速いの?

というわけで、上記記事のコードを元に、以下のコードでベンチマークを取りました。
if array.respond_to?(:sum)としているのは、古いバージョンでも試したためです。

require 'benchmark'

array = 1000.times.map { rand }
num_iteration = 50000

Benchmark.bmbm 10 do |r|
  r.report "inject(&:+)" do
    num_iteration.times do
      sum = array.inject(&:+)
    end
  end

  r.report "inject(:+)" do
    num_iteration.times do
      sum = array.inject(:+)
    end
  end

  r.report "each" do
    num_iteration.times do
      sum = 0
      array.each{|x| sum += x}
    end
  end

  if array.respond_to?(:sum)
    r.report "sum" do
      num_iteration.times do
        sum = array.sum
      end
    end
  end
end

結果

Ruby 2.5.0

                  user     system      total        real
inject(&:+)   4.212000   0.000000   4.212000 (  4.212325)
inject(:+)    2.208000   0.000000   2.208000 (  2.208990)
each          3.700000   0.000000   3.700000 (  3.703916)
sum           0.356000   0.000000   0.356000 (  0.355434)

Ruby 2.3.0

                  user     system      total        real
inject(&:+)   4.920000   0.000000   4.920000 (  4.961357)
inject(:+)    4.500000   0.000000   4.500000 (  4.502774)
each          3.890000   0.000000   3.890000 (  3.892442)

Ruby 1.8.7p357

                  user     system      total        real
inject(&:+)  15.060000   0.000000  15.060000 ( 15.074112)
inject(:+)    6.190000   0.000000   6.190000 (  6.197370)
each         10.650000   0.000000  10.650000 ( 10.669348)

sumが最強

Ruby2.5では他に6倍以上の差を付けてsumの圧勝です。
2.4でも結果は同様でした。

ちなみにsumはFloatの誤差が小さくなるように実装されているので、結果も他3つより正確です。

inject(:+)は2.4で

  1. レシーバーが配列で
  2. 引数がシンボル

の場合の最適化処理が入ったようで、2.5では速くなっています。

2.3ではeachが多少速いですが、何度かやると入れ替わることもあり、あまり大差はない印象です。

もはや誰も使ってないでしょうが、1.8はinject(:+)の圧勝(と言ってもかなり遅い)。ブロック呼び出しが遅いんでしょうか。
いずれにしてもYARVは偉大。

ちなみに

injectのレシーバーが配列、引数がシンボルなら2.4以降では最適化処理が入ると言いましたが、Rubyのソースコードを眺めていると、配列の要素がIntegerだともう一段最適化が入るようです。

そこで、

array = 1000.times.map { rand(1000) }

として、もう一度2.5で試しました。

                  user     system      total        real
inject(&:+)   3.804000   0.000000   3.804000 (  3.823945)
inject(:+)    0.060000   0.000000   0.060000 (  0.058624)
each          2.804000   0.000000   2.804000 (  2.812767)
sum           0.060000   0.000000   0.060000 (  0.059265)

おお、inject(:+)sum並に速くなりました!
整数なら丸め誤差もないですし、inject(:+)もいいかもしれません。

結論

Ruby 2.4以降なら、速くて誤差も小さいArray#sumほぼ一択。ただし要素が全て整数ならinject(:+)もあり。

Ruby2.3までなら、どれもさほど変わらないのでお好きなものを(個人的にはinject(:+)が簡潔で好きです)。

takaram
rakus
「IT技術で中小企業を強くします!」というミッションを掲げ、中小企業の業務効率化に貢献する複数のクラウドサービスを提供しているIT企業です。「楽楽精算」「メールディーラー」など、国内トップシェアを誇る複数のサービスを開発し、累計導入社数は5万社を超えています。次の時代の"楽"を創るための、まだ見ぬサービスや機能を生み出す取り組みは、今日も続いています。
https://www.rakus.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away