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.
August 31st, 2006 at 12:40 pm
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.
August 31st, 2006 at 7:09 pm
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.
November 26th, 2007 at 6:21 pm
[…] 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 […]
November 26th, 2007 at 7:09 pm
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…