If you are used to benchmark your Ruby scripts or if you ever had to improve the performance of some strategic tasks, then this post won't tell you nothing new because you should already know that Exceptions are slow. And this is not really a Ruby problem: .NET Exceptions are slow, JAVA Exceptions are slow just because the begin/raise/rescue (or try/throw/catch) architecture is slow by nature.
But how slow are Ruby Exceptions?
The answer to this question really depends on how complex is your code. Here I just want to show you a very simple example, extracted from a really strategic RoboDomain DNS sorting algorithm.
The code is fairly simple: the value for an A
record is expected to be an IP Address while the value for a NS
record is expected to be represented by a FQDN as string. The code should parse the data
and return the normalized value
depending on some conditions.
The first version of the algorithm is completely based on Exceptions. IPAddr
raises an ArgumentError
when the argument is not a valid IP Address. In this case, the script gracefully returns the value as string.
The second version checks against the record value (I agree with you that is quite empiric, but representative for the sake of this benchmark).
The third version is a less empiric alternative that uses some regular expressions to check whether the data looks like an IP before actually feeding the IPAddr
class. Under the hood, the IPAddr
class performs a really similar task, the only difference here is that in the latter case I'm not using Exceptions at all.
Results speak for themselves.
IPAddr
trying to resolve the hostname. For more benchmarks, also look at this test.
With Ruby 1.8.7, the algorithm (note, this is a super-simplified version of the original algorithm) is about 37 times slower when Exceptions are involved.
$ ruby if_vs_exception.rb
Rehearsal ------------------------------------------------
NS exception 4.330000 5.750000 10.080000 ( 37.599575)
NS if 0.140000 0.010000 0.150000 ( 0.136280)
NS regexp 0.450000 0.000000 0.450000 ( 0.454040)
A exception 2.010000 0.020000 2.030000 ( 2.047711)
A if 2.060000 0.020000 2.080000 ( 2.074642)
A regepx 2.030000 0.020000 2.050000 ( 2.054930)
-------------------------------------- total: 16.840000sec
user system total real
NS exception 4.420000 5.810000 10.230000 ( 38.350608)
NS if 0.130000 0.000000 0.130000 ( 0.130863)
NS regexp 0.450000 0.000000 0.450000 ( 0.447975)
A exception 2.000000 0.020000 2.020000 ( 2.016231)
A if 2.020000 0.020000 2.040000 ( 2.043085)
A regepx 2.030000 0.020000 2.050000 ( 2.048402)
The same story with Ruby 1.9.1.
Rehearsal ------------------------------------------------
NS exception 4.650000 5.800000 10.450000 ( 39.134858)
NS if 0.080000 0.000000 0.080000 ( 0.079427)
NS regexp 0.320000 0.000000 0.320000 ( 0.320389)
A exception 1.180000 0.010000 1.190000 ( 1.182892)
A if 1.200000 0.000000 1.200000 ( 1.209433)
A regepx 1.220000 0.000000 1.220000 ( 1.219345)
-------------------------------------- total: 14.460000sec
user system total real
NS exception 4.520000 5.720000 10.240000 ( 37.931455)
NS if 0.080000 0.000000 0.080000 ( 0.078286)
NS regexp 0.320000 0.000000 0.320000 ( 0.320988)
A exception 1.190000 0.000000 1.190000 ( 1.186198)
A if 1.210000 0.010000 1.220000 ( 1.220254)
A regepx 1.220000 0.000000 1.220000 ( 1.215634)
Does this mean I should forget about Exceptions?
Absolutely no! I love Exceptions and you should love them too. However, Exceptions should not be expected and, in some circumstances, an if
can be much more convenient, or at least, efficient.
One important lesson to learn from these benchmarks is that exceptions should not be part of the regular application flow. This shouldn't prevent you to use them to handle unexpected situations and exception performance shouldn't be a big deal in this case.
A lesson learned from this specific case, is to avoid using exceptions in place of conditional statements to handle not exceptional situations.
UPDATE: for more interesting comments, check out the Reddit page.