Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

Rubyで例外のベンチマークを取ってみた ― やっぱり遅い

Rubyにおいて、例外は他のフロー制御の仕組みに比べて遅いのではないか――私はずっとそう疑ってきました。なにしろ、例外は単純なbreakreturnよりもはるかに複雑な仕組みですから。とはいえ、私の勘が外れたことも過去にはありました。そこで、実際に計測して確かめてみることにしたのです。

以下のコードでは、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

結果は衝撃的

結果はかなり驚くべきものでした。例外を使ったメソッドは、breakreturnを使ったものの半分以下の速度しか出ていません。

$ 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

このベンチマークは完璧ではない

正直なところ、このベンチマークには補正方法がよくわからない問題点がいくつかあります。例えば、exceptionbreakのメソッドは最終的に値を返す必要があるため、単純にreturnするだけのメソッドよりも多くの処理を行っています。また、例外をrescueすること自体がパフォーマンスオーバーヘッドにどれほど影響するのかも気になるところです。しかし、rescueしなければベンチマーク自体が異常終了してしまうため、検証が難しいのが実情です。

それでも、exceptionは他の2つの例と比べてあまりにも遅いため、測定条件が完璧でなくても、この結果には十分意味があると言えるでしょう。

そこから学んだ教訓とは?

例外をフロー制御の仕組みとして使っているなら、今すぐやめましょう! 特に、例外の発生と捕捉を繰り返すようなループを組んでいる場合は深刻なボトルネックになりかねません。

では、これによって私自身の例外の使い方が変わるのでしょうか? おそらく変わらないでしょう。遅さがあくまで「例外(exception to the rule)」である限り、多少の遅さには耐えられますからね :)

……ところで、JRubyやRBxではどうなのか?

Josh Cheek氏(Twitterの@josh_cheek)は、このベンチマークよりもさらに包括的な独自バージョンを作成し、複数のRuby実装に対して実行していました。その結果によると、どうやらbreakが依然として勝者のようです :)

  1. Rubyでカスタム例外を作成する方法【初心者向けガイド】

    Rubyでは、独自の例外クラスを簡単に作成できます。この記事では、カスタム例外を作るための基本的な手順を、コード例とともにわかりやすく解説します。 1. 新しいクラスを作成する Rubyにおいて、例外も他のオブジェクトと同じくクラスとして定義されています。新しい種類の例外を作りたい場合は、StandardError、あるいはそのサブクラスを継承したクラスを定義するだけでOKです。 class MyError < StandardError end raise MyError 慣例として、新しく作る例外クラスの名前は「Error」で終わるように命名します。また、カスタム例外をモジュ

  2. Rubyで例外にコンテキストデータを追加する3つの方法

    標準のバックトレースやエラーメッセージだけでは、エラーの原因を特定するのに十分な情報が得られないことがあります。そんなときに役立つのが、例外への追加データの付与です。幸い、Rubyではこれをとても簡単に実現できます。 エラーメッセージをカスタマイズする 例外にコンテキスト情報を加える最もシンプルな方法は、メッセージそのものに情報を組み込んでしまうことです。次の例では、発生した例外を捕捉し、新しいメッセージを付けて再送出しています。 begin raise foo rescue => e raise e.class, bar end # RuntimeError: bar