しょんぼり技術メモ

まいにちがしょんぼり

Rubyのcatch句のコストはそこまで安くない

制御構造を適当にやっつけで作るときに(たまに)便利なcatch句のコストは、あまり安いわけではなさそうだ。
以下のベンチマークが妥当かどうかは別として、catchじゃなくてもできる処理ならば、catchを書かずに実現した方がたぶん早い。
ほとんどthrowする必要がない(けどたまに必要になるかもしれない)ようなケースでは、ifで書けるならifで書いた方がよさそうだ。
(そもそも、catch構文は複雑な制御構造からスマートに抜け出すためのものだと思っているので、使い道が違うと言われればそれまでだが)

ベンチマークの処理は、1,000,000回インクリメントするだけの処理。
これをcatch句で囲む(んで何もthrowしない)場合と比較する。倍ぐらい違う。

throwの方についても、カウンタが奇数だったらthrowする(その後に何も処理を書いていないので書かなくても同じだが)場合と、カウンタが奇数だったらnextで次のループに抜ける(throwと同じ結果になる)場合を比較する。少し見当違いなベンチマークになっている気がするが気にしない。これだと、throwするコストがさらに増えて4倍ぐらいになる。

$ ruby ./catchtest.rb 
RUBY_VERSION: 1.9.2
loop count: 1000000

without catch block...
=> 0.148608917

with catch block...
=> 0.296118051

with catch block, throw when the number is odd...
=> 0.595503125

without catch block, call next when the number is odd...
=> 0.142759629

胡散臭いベンチマークコード。

num     = 1_000_000
counter = 0

puts "RUBY_VERSION: #{RUBY_VERSION}"
puts "loop count: #{num}"
puts

puts "without catch block..."
bef=Time.now
num.times do
  counter += 1
end
puts "=> #{Time.now - bef}"
puts

puts "with catch block..."
bef=Time.now
num.times do
  catch(:test_catch) do
    counter += 1
  end
end
puts "=> #{Time.now - bef}"
puts

puts "with catch block, throw when the number is odd..."
bef=Time.now
num.times do
  catch(:test_catch) do
    counter += 1
    throw :test_catch if (counter % 2 == 1)
  end
end
puts "=> #{Time.now - bef}"
puts

puts "without catch block, call next when the number is odd..."
bef=Time.now
num.times do
    counter += 1
    next if (counter % 2 == 1)
end
puts "=> #{Time.now - bef}"

とりあえず、めんどくさいからって適当にcatchを書くのはやめようと思った。