I've always strongly suspected that exceptions would be slow in ruby compared to other flow control mechanisms. After all - exceptions are a heck of a lot more complicated than a simple "break" or "return." But I've been wrong in my hunches before, so I thought I'd put it to the test.

In the code below I'm using the benchmark-ips gem to compare the relative performance of exiting a loop via exception, break and return. I've seen examples on the web of people doing benchmarks like this with mri 1.9. But I wanted to try it out with 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

The results are pretty staggering. The function using the exception is less than half as fast as those using break and 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

This isn't a perfect benchmark

There are a couple of issues that I'm not sure how to compensate for. For example, the exception and break methods have to return. So they're doing more than the method which simply returns. Also I'd be interested to see if rescuing the exception adds to the performance overhead. But not rescuing it causes the benchmark to abort.

Still, exception is so much slower than the other examples that I think the results have meaning even if they're not perfect.

The lesson we learned?

If you're using exceptions as a flow control mechanism. Stop now! Especially if you have a loop consisting of exceptions being raised and caught over and over.

Will this change how I personally use exceptions? Probably not. I can live with a little slowness if the slowness is an exception to the rule. :)

...But what about JRuby and RBx?

Josh Cheek (@josh_cheek on twitter) wrote his own version of this benchmark which is more comprehensive then mine. And he ran it against multiple ruby implementations. You can see his results here. Apparently break is still the winner. :)