はじめに
RailsでSeedデータを作成していた時に、「関連テーブルの主キー(id)を全て取得する際に何のメソッドを使うべきか?」考えたのがきっかけでした。
今回はメソッドの実行速度を指標として考えてみました。
⚠︎これが正解というわけではなく、あくまで私個人の見解です。
今回使用したメソッド
pluck
- 特徴:指定したカラムのレコードの配列を取得する
- メリット: ActiveRecordオブジェクトを生成せず、特定のカラムのみデータベースから取り出すためメモリを節約できる。
- デメリット:メソッド実行時に毎回SQLが発行される。
user_ids = User.pluck(:id)
=> [1, 2, 3, 4, 5, ...] # 返り値
ids
- 特徴:主キーのカラムデータを全て配列で取得する
- メリット:レコードのIDの配列の取得に適している。
- デメリット:idの取得のみを行うため、他の属性の値は取得できない。
user_ids = User.ids
=> [1, 2, 3, 4, 5, ...] # 返り値
map
- 特徴:各要素に対してブロックを評価した結果を全て含む配列を返す。
- メリット:元の配列を変更せずに新しい配列を生成できるので、元のデータを保持しつつ、処理された結果を取得できる。
- デメリット:レシーバをメモリ に読み込むためメモリを浪費する。
user_map = User.all.map(&:id)
#もしくは
user_map = User.all.map(|user| user.id)
=> [1, 2, 3, 4, 5, ...] # 返り値
計測の仕方
railsのライブラリbenchmarkを使用しました。
サンプルコードはこちらです。
require 'benchmark'
result_ids = Benchmark.bm do |x|
x.report("ids:"){user_ids = User.ids}
end
result_pluck = Benchmark.bm do |x|
x.report("pluck:"){user_pluck = User.pluck(:id)}
end
result_map = Benchmark.bm do |x|
x.report("map:"){user_map = User.all.map(&:id)}
end
-
x.report
: 測定対象のコードブロックとラベルを指定します。 -
("")
の中でラベルを指定できます。 -
{}
の中に測定したい処理を記述します。
実行結果
user system total real
ids: 0.000000 0.000894 0.000894 ( 0.001304)
user system total real
pluck: 0.000425 0.000149 0.000574 ( 0.000716)
user system total real
map: 0.000727 0.000183 0.000910 ( 0.001290)
totalの値で評価しています。
処理速度の観点だけで見れば、pluckに軍配が上がりました。
結果の見方
「【Ruby】まとめでよく見る処理速度の計測方法・見方」の記事を参考にしています。
user : ユーザー CPU 時間
ユーザープログラムの実行に費やした CPU 時間。この場合、Ruby スクリプトを実行すべく Ruby 処理系が働いたぶんの CPU 時間のこと。
system : システム CPU 時間
OS はファイルの読み書きなどの基本的な機能を「システムコール」という形で提供しているが、プログラムがそれを呼び出しその実行に費やした CPU 時間をこう呼ぶらしい。
total : userとsystemの和
real : 処理の開始から終了までの現実の経過時間
Timeクラスを使用した場合と同じなので、待機時間が含まれたりネットワークの影響も受けてしまうため、見る必要はない。
おわりに
処理速度を調べてみて、各メソッドへの理解が深まりました。
今回の結果ではpluckが一番速かったのですが、idsのほうが速いという説もあるようです。
DBの設定やデータ量などのコンテキストの影響もあると思うので、単純な処理速度だけでなくメソッドの用途や特性を理解した上で選定できるようになりたいです。