LoginSignup
98
60

More than 5 years have passed since last update.

pluckよりもmapのほうが高速なケース

Last updated at Posted at 2015-01-11

ActiveRecord Relationから特定のカラムの配列を取得したいとき、基本的にはmapではなくpluckを使うべきです。

References

ただし、pluckはインスタンス化されたオブジェクトに対しても毎回SELECT文を投げてしまうようなので注意です。

pluckを使うべきケース

pluckは必要なカラムのみのSELECTに絞られているため、DB-アプリ間の消費帯域節約やカバリングインデックスが使えた場合などの観点でSQLパフォーマンスが有利です。
また、activerecordオブジェクトを生成せずに値を返すためインスタンス生成オーバーヘッドやメモリ消費の観点でも有利です。
DBから値をとってくる必要があるケースではpluckを使うべきです。

Profile.all.pluck(:id)
  # => SELECT "profiles"."id" FROM "profiles"
Profile.all.map(&:id)
  # => SELECT "profiles".* FROM "profiles"

Benchmark

n = 2000
Benchmark.bm do |x|
  x.report { n.times { Profile.all.pluck(:id) } }
  x.report { n.times { Profile.all.map(&:id) } }
end
user system total real
pluck(:id) 2.030000 2.580000 4.610000 ( 7.576141)
map(&:id) 4.420000 2.850000 7.270000 ( 10.10201)

mapを使うべきケース

以下のように既にインスタンス化されたオブジェクトから値をぬきとりたい場合は、pluckだとかなり問題があります。
pluckの場合、毎回SELECT文が発行されてしまうので、ループで実行したりするとN+1問題みたいになります。
(ちなみに、eager_loadやincludeなどをかませてpluckでもSQL発行されない方法を探ってみましたがだめでした。ご存知のかたがいれば教えてください!)

一方mapの場合はメモリ内で計算するだけになりコストはほぼゼロです。

profiles = Profile.all
n = 2000
Benchmark.bm do |x|
  x.report { n.times { profiles.pluck(:id) } }
  x.report { n.times { profiles.map(&:id) } }
end
user system total real
pluck(:id) 2.020000 2.400000 4.420000 ( 7.245241)
map(&:id) 0.000000 0.000000 0.000000 ( 0.007601)

Environment

※ HostOSはMac OSXで、virtualboxのvagrant NFSモードでCentOS 6.5を起動してます
※ virtualboxだからかGuestOSでのディスクIOがかなり遅いのでたった2000件のテストですごい差が出ています
※ ベンチマークテストでのDBはsqlite3を使ってますが、mysqlでもほぼ同じ結果が出ることを確認しました

pry(main)> Rails.version
=> "4.1.6"
% uname -a
Linux *** 2.6.32-431.el6.x86_64 #1 SMP Fri Nov 22 03:15:09 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux
% cat /etc/issue
CentOS release 6.5 (Final)
Kernel \r on an \m
98
60
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
98
60