2014/02/20

いろんな型の範囲に対する範囲内チェックメソッドのパフォーマンスチェックをやってみた

先日書いた 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よりも倍近く高速なのが興味深い。

0 件のコメント :

コメントを投稿