Edited at

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

More than 3 years have passed since last update.

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