Rubyで例外のベンチマークを取ってみた ― やっぱり遅い
Rubyにおいて、例外は他のフロー制御の仕組みに比べて遅いのではないか――私はずっとそう疑ってきました。なにしろ、例外は単純なbreakやreturnよりもはるかに複雑な仕組みですから。とはいえ、私の勘が外れたことも過去にはありました。そこで、実際に計測して確かめてみることにしたのです。
以下のコードでは、benchmark-ipsというgemを使い、「例外でループを抜ける」「breakで抜ける」「returnで抜ける」という3つの手法の相対的なパフォーマンスを比較しています。Web上ではMRI 1.9で同様のベンチマークを行った例を見かけましたが、今回はMRI 2.2で試してみました。
require 'benchmark/ips'
def exit_via_exception
5.times do
raise RuntimeError
end
rescue
end
def exit_via_break
5.times do
break
end
end
def exit_via_return
5.times do
return
end
end
Benchmark.ips do |x|
x.report("exception") { exit_via_exception }
x.report("break") { exit_via_break }
x.report("return") { exit_via_return }
end
結果は衝撃的
結果はかなり驚くべきものでした。例外を使ったメソッドは、breakやreturnを使ったものの半分以下の速度しか出ていません。
$ ruby exception_benchmark.rb
Calculating -------------------------------------
exception 50.872k i/100ms
break 125.322k i/100ms
return 124.173k i/100ms
-------------------------------------------------
exception 714.795k (± 2.7%) i/s - 3.612M
break 3.459M (± 3.1%) i/s - 17.294M
return 3.379M (± 3.0%) i/s - 16.888M
このベンチマークは完璧ではない
正直なところ、このベンチマークには補正方法がよくわからない問題点がいくつかあります。例えば、exceptionとbreakのメソッドは最終的に値を返す必要があるため、単純にreturnするだけのメソッドよりも多くの処理を行っています。また、例外をrescueすること自体がパフォーマンスオーバーヘッドにどれほど影響するのかも気になるところです。しかし、rescueしなければベンチマーク自体が異常終了してしまうため、検証が難しいのが実情です。
それでも、exceptionは他の2つの例と比べてあまりにも遅いため、測定条件が完璧でなくても、この結果には十分意味があると言えるでしょう。
そこから学んだ教訓とは?
例外をフロー制御の仕組みとして使っているなら、今すぐやめましょう! 特に、例外の発生と捕捉を繰り返すようなループを組んでいる場合は深刻なボトルネックになりかねません。
では、これによって私自身の例外の使い方が変わるのでしょうか? おそらく変わらないでしょう。遅さがあくまで「例外(exception to the rule)」である限り、多少の遅さには耐えられますからね :)
……ところで、JRubyやRBxではどうなのか?
Josh Cheek氏(Twitterの@josh_cheek)は、このベンチマークよりもさらに包括的な独自バージョンを作成し、複数のRuby実装に対して実行していました。その結果によると、どうやらbreakが依然として勝者のようです :)
-
Rubyでカスタム例外を作成する方法【初心者向けガイド】
Rubyでは、独自の例外クラスを簡単に作成できます。この記事では、カスタム例外を作るための基本的な手順を、コード例とともにわかりやすく解説します。 1. 新しいクラスを作成する Rubyにおいて、例外も他のオブジェクトと同じくクラスとして定義されています。新しい種類の例外を作りたい場合は、StandardError、あるいはそのサブクラスを継承したクラスを定義するだけでOKです。 class MyError < StandardError end raise MyError 慣例として、新しく作る例外クラスの名前は「Error」で終わるように命名します。また、カスタム例外をモジュ
-
Rubyで例外にコンテキストデータを追加する3つの方法
標準のバックトレースやエラーメッセージだけでは、エラーの原因を特定するのに十分な情報が得られないことがあります。そんなときに役立つのが、例外への追加データの付与です。幸い、Rubyではこれをとても簡単に実現できます。 エラーメッセージをカスタマイズする 例外にコンテキスト情報を加える最もシンプルな方法は、メッセージそのものに情報を組み込んでしまうことです。次の例では、発生した例外を捕捉し、新しいメッセージを付けて再送出しています。 begin raise foo rescue => e raise e.class, bar end # RuntimeError: bar