The price of a rescue

I’ve been abusing Ruby exception handling lately. Instead of doing something like:

if user and user.group
  user.group.do_something
end

or a cleaner

user and user.group and user.group.do_something

I just do

user.group.do_something rescue nil

And let Ruby deal with the consequences of a missing link.

It was all nice and pretty, until I saw this little guy standing in my shoulder — dressed in white and with a halo over his head — telling me that laziness is wrong and that there would be a price to pay. After all, exception handling requires setting up flags and pointers and blocks and contexts and who knows what else. They must be expensive. And of course, there was another guy — red, with horns and a tail — calling the white guy an asshole and a coward.

In the end I decided to stop taking drugs, and to do my own research into the subject, instead of paying attention to my own hallucinations.

I wrote the simplest test cases, and used the ‘benchmark’ library to measure their performance:

require 'benchmark'

n = 500000
Benchmark.bm(7) do |x|
  x.report("plain") do
    for i in 1..n
      1.0/5.0
    end
  end

  x.report("safe") do
    for i in 1..n
      begin
        1.0/5.0
      rescue
        0.2
      end
    end
  end

  x.report("rescue") do
    for i in 1..n
      begin
        1.0/0.0
      rescue
        0.2
      end
    end
  end
end

It does half-a-million floating point divisions. The first scenario (”plain”) has no exception handling. The second one (”safe”) has exception handling, but never raises an exception. And the final scenario (”rescue”) uses exception handling all the time, triggered by a division by zero.

The results are something like this:

             user     system      total        real
plain    0.780000   0.010000   0.790000 (  1.769231)
safe     0.910000   0.020000   0.930000 (  1.781405)
rescue   0.900000   0.010000   0.910000 (  1.945978)

I know Zed will probably kill me for making any conclusions based on such a small sample (the half-a-million divisions are not the sample size… the sample size is one single run of the test). But the numbers are pretty much the same when you run the tests several times.

Using rescue is not much more expensive than running naked. In my tests in particular, it never was more than 5% slower. It might even be cheaper than multiple tests.

It’s fast enough for me not to worry about it, specially considering the fact that my own operations are probably going to be a lot slower than a simple division, thus diluting any slowdown even further.

If you really care about this, then by all means make your own tests and take your own conclusions. Otherwise, just trust the guy dressed in red, with the horns and the pointy tail: use and abuse rescue until you’re tired of it; it’s not worth worrying about its performance impact.

4 Responses to “The price of a rescue”

  1. Tim Morgan Says:

    I’ve always wondered about this. Thanks for finally putting my fears to rest as well. According to your findings, looks like using rescue instead of ‘if’ may be better overall because it will catch more than just what I remember to test, and like you said, there isn’t much of a slowdown.

  2. Phil Hagelberg Says:

    Yeah, specifying to rescue only the NoMethodErrors is a lot safer. I still don’t think I would do it, but I wouldn’t rewrite sections of code I inherited if I saw that someone else had done it.

  3. The Amazing Blog » Blog Archive » Ruby rescues are not slow Says:

    […] so I think that has given them a bad name everywhere. The only real numbers I could find are from http://www.notsostupid.com/blog/2006/08/31/the-price-of-a-rescue/ . His ‘plain’ test is also missing the conditional that would also have to be executed […]

  4. Sam Smoot Says:

    Only one small problem. Your benchmarks don’t actually raise any exceptions. :-)

    This does: http://pastie.textmate.org/private/g4qs8ae3lo5bnpgpeuhp3a

    The result is that the code that raises exceptions is well over 100 times slower.

    Exceptions should be reserved for exceptional situations. For convenience I break that rule all the time myself. :-) But still…

Leave a Reply