Is 'rescue' expensive ?

R

R. Kumar

If i use something like:
array.count rescue array.size

does the rescue set up anything internally that will make this
inefficient ?

I have other options too, however, i want to minimize code clutter since
this situation is rare (my app says it works under 1.8.7 and 1.9).
Someones using it in 1.8.6 which i learn has no array#count. Otoh, 1.9
has no array#size.

Other options are :

unless Array.method_defined?:)count) then
class Array
def count
size
end
end
end
# remark: i assume this is a one time startup cost


or:

if array.respond_to?:)count) then
array.count
else
array.size
end
# remark: evaluated each time, so cost is high

My hope is that the rescue clause of the first example is only looked at
by the interpreter if an error occurs, otherwise there's no penalty for
it.
Suggestions ?
 
I

Iñaki Baz Castillo

El Martes, 12 de Enero de 2010, R. Kumar escribi=C3=B3:
If i use something like:
=20
does the rescue set up anything internally that will make this
inefficient ?
=20
I have other options too, however, i want to minimize code clutter since
this situation is rare (my app says it works under 1.8.7 and 1.9).
Someones using it in 1.8.6 which i learn has no array#count. Otoh, 1.9
has no array#size.

Ruby 1.9 has Array#size, sure.

Other options are :
=20
unless Array.method_defined?:)count) then
class Array
def count
size
end
end
end
# remark: i assume this is a one time startup cost
=20
=20
or:
=20
if array.respond_to?:)count) then
array.count
else
array.size
end
# remark: evaluated each time, so cost is high
=20
My hope is that the rescue clause of the first example is only looked at
by the interpreter if an error occurs, otherwise there's no penalty for
it.
Suggestions ?

=46orgetting the fact tha Array#size does exist iunder 1.9, I think that=20
defining the method "count" (in case it doesn't exist) is a bit faster:

Ruby allows redefining methods in runtime which means that when a method is=
=20
invoked the interpreter must look for it and execute its code. Then:

array.count rescue array.size

=2D First it involves looking for "count" method.
=2D It doesn't exist so raises.
=2D Then "size" is invoked so it involves looking for "size" method.


unless Array.method_defined?:)count) then
class Array
def count
size
end
end
end

In this case:
=2D First "count" is looked for.
=2D It's found and "size" is invoked so "size" method is looked for.

It's faster as you avoid the rescue.





=2D-=20
I=C3=B1aki Baz Castillo <[email protected]>
 
R

R. Kumar

Iñaki Baz Castillo said:
El Martes, 12 de Enero de 2010, R. Kumar escribió:
I looked at http://yardoc.org/docs/ruby-core/Array and it did not show a
`size` method.
Perhaps that's been aliased and the doc does not report aliases. Best
thing is for me to try it in 1.9!

irb(main):023:0> RUBY_VERSION
"1.9.1"

irb(main):025:0> RUBY_PATCHLEVEL
376

irb(main):024:0> [].size
0


:)

Yes, i just checked myself. "size" matters :).

If in 99 pc cases, "count" works, then the rescue is only read by the
interpreter in the 1% case where it fails. Is that right?


(Anyway, for this particular case, i am changing count to size)
 
K

Kirk Haines

If i use something like:


does the rescue set up anything internally that will make this
inefficient ?

To address the original question, the use of rescue does impart a
small overhead, but unless you are min-maxing performance in a tight
loop somewhere, you can safely disregard the amount of overhead. This
is also something you can readily test for yourself, BTW:


Benchmark.bm {|bm| bm.report {10000000.times {a.size rescue 'stuff'}}}
user system total real
1.480000 0.000000 1.480000 ( 1.476450)

Benchmark.bm {|bm| bm.report {10000000.times {a.size}}}
user system total real
1.280000 0.000000 1.280000 ( 1.283498)



Kirk Haines
 
I

Iñaki Baz Castillo

El Martes, 12 de Enero de 2010, R. Kumar escribi=C3=B3:
Yes, i just checked myself. "size" matters :).
=20
If in 99 pc cases, "count" works, then the rescue is only read by the=20
interpreter in the 1% case where it fails. Is that right?

IMHO yes=20


=2D-=20
I=C3=B1aki Baz Castillo <[email protected]>
 
I

Iñaki Baz Castillo

El Martes, 12 de Enero de 2010, I=C3=B1aki Baz Castillo escribi=C3=B3:
El Martes, 12 de Enero de 2010, R. Kumar escribi=C3=B3:
=20
IMHO yes

=2E..but I was wrong as Kirk has explained in a new thread :)=20


=2D-=20
I=C3=B1aki Baz Castillo <[email protected]>
 
A

Albert Schlef

R. Kumar said:
If i use something like:


does the rescue set up anything internally that will make this
inefficient ?

rescue's impact is negligible.

HOWEVER, that's NOT the question you need to ask. You need to ask how
costly is hitting on a nonexistent method. See my benchmark (I do have
Array#size on my system):

Benchmark.bm {|bm| bm.report {100000.times {a.size rescue a.size}}}
user system total real
0.110000 0.000000 0.110000 ( 0.219765)

Benchmark.bm {|bm| bm.report {100000.times {a.not_found rescue a.size}}}
user system total real
30.450000 0.520000 30.970000 ( 33.876875)

See? On my system, hitting a nonexistent method is 300 times slower than
existing one.
Other options are :

unless Array.method_defined?:)count) then
class Array
def count
size
end
end
end

It'd be faster if you use the 'alias' keyword instead of wrapping the
call in a function of your own because then you save one function call.
 
R

R. Kumar

Albert said:
It'd be faster if you use the 'alias' keyword instead of wrapping the
call in a function of your own because then you save one function call.

Thanks a lot for your answer. This brings up a question on usage of
"rescue". Is it a "code smell" or bad programming?

I recall i've used it in some places where often different datatypes can
come in. Forgetting this, over time , I've added a ".length()" check or
".trim()", "chomp()" etc.

Suddenly, when boolean data or Fixnum comes these lines fail, so I tend
to add a rescue clause. And these are typically in a loop.
Currently, the "rescue" would work for various cases and datatypes. If I
open classes and start adding aliases or methods, I could have to do it
over and over again.

I would appreciate some guidelines on rescue usage.
 
K

Kirk Haines

Thanks a lot for your answer. This brings up a question on usage of
"rescue". Is it a "code smell" or bad programming?

"Code smell" is a weak concept that people use when they can't give
clear, concise reasons why they don't like something about a piece of
code.

There is nothing wrong with "rescue". It's an essential part of
writing good code, in fact. That doesn't mean it can't be misused,
though.
I recall i've used it in some places where often different datatypes can
come in. Forgetting this, over time , I've added a ".length()" check or
".trim()", "chomp()" etc.

You can use a respond_to? check in your code to see if you are dealing
with an object that supplies the methods you are expecting. This is
often more useful than just calling a method and rescuing its failure.

Also, one thing to be aware of is that while a rescue clause itself
confers only a modest overhead, if you are actually triggering
exceptions that are rescued often, that confers a substantial
overhead. There are some time consuming things, such as constructing
the backtrace, that go into exceptions, so the fewer exceptions your
code raises, the better, generally.
Suddenly, when boolean data or Fixnum comes these lines fail, so I tend
to add a rescue clause. And these are typically in a loop.
Currently, the "rescue" would work for various cases and datatypes. If I
open classes and start adding aliases or methods, I could have to do it
over and over again.

There isn't a clear, well defined answer to this. It really depends
on the architecture of your overall code. I would suggest, though,
that if you end up having a lot of lines with rescue clauses at the
end of them, you are probably doing something wrong and need to step
back and look at how you can adjust your code to consolidate the
places where you employ rescue clauses, whether that's by making your
code smarter elsewhere, or by using the begin/rescue/end(else) syntax
to apply a single rescue clause to a broader swath of code.


Kirk Haines
 
R

Rick DeNatale

One of the things that seems to be mostly missing from this discussion
(which after all started out with dealing with a difference between
Ruby 1.8 and 1.9, is that since Ruby class definitions are executable,
you can move the overhead to class definition time with code like

class Foo
def some_version_independent method
#...
end
if RUBY_VERSION =~ /^1\.9/
def some_version_dependent_method
#implementation which works in 1.9
end
else
def some_version_dependent_method
#implementation which works in 1.8
end
end
end



--
Rick DeNatale

Blog: http://talklikeaduck.denhaven2.com/
Twitter: http://twitter.com/RickDeNatale
WWR: http://www.workingwithrails.com/person/9021-rick-denatale
LinkedIn: http://www.linkedin.com/in/rickdenatale
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,995
Messages
2,570,230
Members
46,819
Latest member
masterdaster

Latest Threads

Top