配列などの集合インスタンスの要素数を取得する時に #size
#length
または #count
を使っているコードをよく見ます。よく見るが故に、違いがなんなのか気になったので調査・検証してみました。
結論
-
#size
,#length
はレシーバがすでに持っている要素数情報を参照する -
#count
はEnumerableモジュールで定義されているメソッドで、実際にeach処理を回してカウントした結果を出力している
よって、まとめると 要素数が多いインスタンスで count
を実行すると遅くなる
解説
実際に計測してみた
こんなコードを書いて検証してみた。
100万の要素数を持つ配列を使って、それぞれ100万回繰り返して各メソッドを実行してみました。
require 'benchmark'
num = 1_000_000
a = []
num.times.to_a
Benchmark.bm 10 do |r|
r.report '#size' do
num.times do
a.size
end
end
r.report '#length' do
num.times do
a.length
end
end
r.report '#count' do
num.times do
a.count
end
end
end
結果がこのようになりました。
user system total real
#size 0.060000 0.000000 0.060000 ( 0.066027)
user system total real
#length 0.050000 0.000000 0.050000 ( 0.054165)
user system total real
#count 0.080000 0.000000 0.080000 ( 0.080645)
user system total real
#size 0.050000 0.000000 0.050000 ( 0.051824)
user system total real
#length 0.050000 0.000000 0.050000 ( 0.055952)
user system total real
#count 0.070000 0.000000 0.070000 ( 0.081042)
user system total real
#size 0.050000 0.000000 0.050000 ( 0.054816)
user system total real
#length 0.050000 0.010000 0.060000 ( 0.056448)
user system total real
#count 0.080000 0.000000 0.080000 ( 0.085763)
以降、繰り返ししても #count
が遅いことに変わりはありませんでした。
なぜ #count
は遅いのか?
#size``#length
はレシーバの持っている要素情報を参照しているから早いです。一方で #count
は #each
を実行して数え上げているので、前者と比べて余計な処理をしていることになる。なので、時間がかかってしまいます。
#count
は使わないほうがよいのか?
いえ、そういうわけではありません。結果をみると、わずかに遅いだけなので致命的な問題になることはあまりないでしょう。また、繰り返し処理を実行しているということが役立つときもあります。例えば、条件に一致した要素だけ数えあげる、といった場合などですね。
a = [1,3,5,7,9,11,13,17,19,23].count { |i| i > 9 }
puts a # 5
#count
はブロック引数を持たせることができるので、このような抽出が可能なのです。
ただし、単純に要素数を参照したいだけであれば #size
, #length
を使うのが好ましいですね。