やわらかテック

興味のあること。業務を通して得られた発見。個人的に試してみたことをアウトプットしています🍵

【Ruby】配列からハッシュを生成する4つの方法と速度比較

Rubyで開発をしていると配列からハッシュ(連想配列)を生成するコードを書くことがあります。
個人的にはreduce関数を使ってバシッと書くのが気持ちよくて好きなのですが、最近、人によってこの処理の書き方が違うことに気づきました。

4つの書き方

僕が個人的に観測しているのは以下の4つの書き方(方法)です。

  • for inを使った方法
  • each関数を使った方法
  • each_with_object関数を使った方法
  • reduce関数を使った方法

それぞれ、簡単なコードを再現してみました。
処理は非常にシンプルで配列の各要素を文字列にした値をハッシュのキーとし、各要素を2倍にした値をバリューとします。

for inを使った方法

list = (1..10000).to_a
hash = {}
for n in list do
  hash[n.to_s] = n * 2
end

each関数を使った方法

list = (1..10000).to_a
hash = {}
list.each do |n|
  hash[n.to_s] = n * 2
end

each_with_object関数を使った方法

list = (1..10000).to_a
list.each_with_object({}) do |n, hash|
  hash[n.to_s] = n * 2
end

reduce関数を使った方法

list = (1..10000).to_a
list.reduce({}) do |hash, n|
  hash[n.to_s] = n * 2; hash
end

では、この4つのコードのパフォーマンスを計測してみます。

計測結果

計測には標準ライブラリのbenchmarkを使用しました。単位は秒です。
計測に使用したコードはこちらにて公開しているので、気になる方は覗いてみてください。

                        user     system      total        real
for in:                0.003110   0.000189   0.003299 (  0.003295)
each:                  0.001693   0.000066   0.001759 (  0.001761)
each_with_object:      0.002402   0.000057   0.002459 (  0.002477)
reduce:                0.002407   0.000021   0.002428 (  0.002430)

計測結果を見てみると、4つの方法の実行時間にはあまり差がないことが分かります。
ただ、system時間を見てみるとreduce関数が最も短く、for inが最も時間が長いようですが、内部実装に違いがあるのでしょうか。JavaScriptで連番の配列の生成速度を比較した時は逆の結果になりました。
非常に興味深い結果ですが、Rubyのコードを深掘りしないと答えには辿り着けなさそうです。

www.okb-shelf.work

勝手にeach関数はfor inもしくはwhile文を内部で呼び出していると思っていたので、each関数が最も速いという結果が出たことには驚きました。その他のeach_with_object・reduce関数はほぼ誤差なので、好みの問題というレベルでした。

結論

for in以外なら好きなものを使えば良いと思います。
今回はメモリ使用率やCPU負荷については計測していないのですが、この程度のデータ量であれば、大きな違いはないでしょう。チームで「この書き方にしよう」と決めれば何でも良いですね。個人的には変数宣言も不要で、汎用性が高いreduce関数を推していきます。each_with_object関数は便利ですが、このためだけに1つ関数を覚えないといけないというのが個人的には「何だかな...」と感じるポイントです。

少しでも「ええな〜」と思ったらイイネ!・シェア!・はてなブックマークを頂けると励みになります。