先日書いた pinzolo/tekido を開発していた時、どうも一部時間のかかる処理があった。
割合系の spec を確認するために、誤差1%で合格する spec を書いていたんだけど、各sampleで試行回数10000回だとどうしても、ちょくちょく誤差を超えてしまう。
試行回数を10倍の100000回で行うと安定して、誤差内でおさまるんだけどテストの実行に結構な時間がかかっていた。
調べてみると、どうやら日付の Range
に対しての include
マッチャが時間がかかっている。
実際はマッチャは関係なく、include?
メソッドの実行が結構重い。
ドキュメントによると、cover?
だと、両端の <=> 比較によるチェックを行うということなので置き換えてみるとずいぶんと高速になったので解決はした。
そんなこともあって、せっかくなのでいろんな Range
に対して include?
, member?
, ===
, cover?
のパフォーマンスを測ってみた。
環境
MacBook Air 13-inch, Mid 2012
プロセッサ : 2GHz Intel Core i7
メモリ : 8GB 1600 MHz DDR3
ソフトウェア : OS X 10.9.1 (Mavericks)
Ruby : ruby 2.0.0p353
数値
# coding: utf-8 | |
require 'benchmark' | |
range = 1..5000 | |
try_count = 10000 | |
Benchmark.bm(8) do |bm| | |
bm.report('include?') do | |
try_count.times do | |
range.include?(rand(10000)) | |
end | |
end | |
bm.report('member?') do | |
try_count.times do | |
range.member?(rand(10000)) | |
end | |
end | |
bm.report('===') do | |
try_count.times do | |
range === rand(10000) | |
end | |
end | |
bm.report('cover?') do | |
try_count.times do | |
range.cover?(rand(10000)) | |
end | |
end | |
end | |
# user system total real | |
# include? 0.010000 0.000000 0.010000 ( 0.006155) | |
# member? 0.010000 0.000000 0.010000 ( 0.006968) | |
# === 0.010000 0.000000 0.010000 ( 0.008144) | |
# cover? 0.000000 0.000000 0.000000 ( 0.007070) |
文字列
# coding: utf-8 | |
require 'benchmark' | |
range = 1.chr("UTF-8")..5000.chr("UTF-8") | |
try_count = 10000 | |
Benchmark.bm(8) do |bm| | |
bm.report('include?') do | |
try_count.times do | |
range.include?(rand(10000).chr("UTF-8")) | |
end | |
end | |
bm.report('member?') do | |
try_count.times do | |
range.member?(rand(10000).chr("UTF-8")) | |
end | |
end | |
bm.report('===') do | |
try_count.times do | |
range === rand(10000).chr("UTF-8") | |
end | |
end | |
bm.report('cover?') do | |
try_count.times do | |
range.cover?(rand(10000).chr("UTF-8")) | |
end | |
end | |
end | |
# user system total real | |
# include? 4.100000 0.010000 4.110000 ( 4.132348) | |
# member? 4.070000 0.010000 4.080000 ( 4.098094) | |
# === 3.930000 0.000000 3.930000 ( 3.949908) | |
# cover? 0.010000 0.000000 0.010000 ( 0.007477) |
cover?
だけど、10000回で4秒ちょい、1回あたり0.4ミリ秒なら include?
が使われててもまあ別にいいかなと思うレベル。(状況にもよるけど)
日付
# coding: utf-8 | |
require 'date' | |
require 'benchmark' | |
begin_date = Date.today << 12 * 10 | |
end_date = Date.today | |
range = begin_date..end_date | |
try_count = 10000 | |
Benchmark.bm(8) do |bm| | |
bm.report('include?') do | |
try_count.times do | |
date = Date.today - rand(2000) | |
range.include?(date) | |
end | |
end | |
bm.report('member?') do | |
try_count.times do | |
date = Date.today - rand(2000) | |
range.member?(date) | |
end | |
end | |
bm.report('===') do | |
try_count.times do | |
date = Date.today - rand(2000) | |
range === date | |
end | |
end | |
bm.report('cover?') do | |
try_count.times do | |
date = Date.today - rand(2000) | |
range.cover?(date) | |
end | |
end | |
end | |
# user system total real | |
# include? 24.040000 0.050000 24.090000 ( 24.167171) | |
# member? 24.160000 0.050000 24.210000 ( 24.288291) | |
# === 24.150000 0.050000 24.200000 ( 24.282828) | |
# cover? 0.020000 0.000000 0.020000 ( 0.021473) |
ということで
範囲内チェックは一律 cover?
を使うのが無難そうです。
RSpec にも cover
マッチャがあるので単純に置き換えできます。
ちなみに、Windows7 32bit環境(MBAよりもスペックは低い)で試してみたら、数値と文字列は変わらなかったけど日付の上三つの処理時間は13秒ぐらいでした。
MBAよりも倍近く高速なのが興味深い。