Contest: fastet way to redefine a method


#1

Hi,

Dan B.'s recent thread about using UnboundMethod#bind to redefine an
existing method reminded me of a problem I still have:

What is the fastest way to redefine an existing method?

With “fast” I don’t mean the process of redefining the method, but the
time it takes to actually call the new implementation.

With “redefining an existing method” I mean changing the implementation
of an existing method so that in the new implementation it is possible
to call the old one and use its return value.

I’ve seen many different techniques in the past here on ruby-talk, but I
haven’t looked at the tradeoffs. I promise to write a page on the ruby
garden wiki with the results.

Regards,
Pit


#2

Pit C. wrote:

With “redefining an existing method” I mean changing the implementation
of an existing method so that in the new implementation it is possible
to call the old one and use its return value.

I’ve seen many different techniques in the past here on ruby-talk, but I
haven’t looked at the tradeoffs. I promise to write a page on the ruby
garden wiki with the results.

Regards,
Pit

A cut is going to be just about the fastest I think since it is
essentially a subclass, although defining the “wrapping” method
requires a bit of additional overhead. Alias certainly has the least
overhead. I imagine before and after wraps as matz has suggested for
Ruby 2.0 would be faster, albiet they are more limited in capability
than cuts.

Keep in mind that every “wrap” will have a benchmark greater than the
orginal by neccessity b/c includes the originals call --so what’s
really of interest is the difference from the original. Consdiering
that it is clear what the fastest way would be. To literally extract
the source code of the oringal method and wrap it via string
manipulation and eval the result as the new method. Of course, that’s
not all that practical – for starters I think you would need something
like ParseTree to even pull it off.

Anyway here’s Ara’s benchmarks with cuts/subclass added:

Alias

class HashUsingAlias < Hash
alias :old_hset :[]=

def []=(key, value)
  self.old_hset(key, value)
end

end

Bind

class HashUsingBind < Hash
hset = self.instance_method(:[]=)

define_method(:[]=) do |key, value|
  hset.bind(self).call(key, value)
end

end

Override

require ‘override’
class HashUsingOverride < Hash
override(’[]=’){ def []=(k,v) super end }
end

Subclass

class HashUsingSubClass < Hash
def []=(k,v)
super
end
end

Cut (pure ruby meta-hacking version)

require ‘facets/more/cut’
class HashUsingCut < Hash; end

cut :HashUsingCutAspect < HashUsingCut do
def []=(k,v); super; end
end

require “benchmark”
def bm_report bm, title, hash_class
hash = hash_class.new
bm.report title do
100_000.times do
hash[ 1 ] = 1
end
end
end

Benchmark.bmbm do |bm|
bm_report bm, “original”, Hash
bm_report bm, “alias”, HashUsingAlias
bm_report bm, “bind”, HashUsingBind
bm_report bm, “override”, HashUsingOverride
bm_report bm, “subclass”, HashUsingSubClass
bm_report bm, “cut”, HashUsingCut
end

Rehearsal --------------------------------------------
original 0.100000 0.020000 0.120000 ( 0.125107)
alias 0.180000 0.030000 0.210000 ( 0.226911)
bind 0.460000 0.050000 0.510000 ( 0.525037)
override 0.590000 0.030000 0.620000 ( 0.630301)
subclass 0.170000 0.030000 0.200000 ( 0.210436)
cut 0.170000 0.030000 0.200000 ( 0.210003)
----------------------------------- total: 1.860000sec

           user     system      total        real

original 0.100000 0.010000 0.110000 ( 0.123498)
alias 0.170000 0.040000 0.210000 ( 0.224580)
bind 0.480000 0.030000 0.510000 ( 0.529366)
override 0.570000 0.050000 0.620000 ( 0.626580)
subclass 0.170000 0.030000 0.200000 ( 0.214458)
cut 0.170000 0.030000 0.200000 ( 0.209727)


#3

On 27.12.2006 17:59, Pit C. wrote:

What is the fastest way to redefine an existing method?

With “fast” I don’t mean the process of redefining the method, but the
time it takes to actually call the new implementation.

So you mean runtime efficiency.

With “redefining an existing method” I mean changing the implementation
of an existing method so that in the new implementation it is possible
to call the old one and use its return value.

I once cooked something together. The code is on Ruby Garden:
http://wiki.rubygarden.org/Ruby/page/show/MethodHooks

No idea about performance etc. At some point I stopped working on this
because it was said that Ruby 2 will include a feature for this. I
remember that there were some limitations (i.e. no suppport for blocks
so far) but usability is ok IMHO. This was mostly an experiment.

Kind regards

robert

#4

On Dec 27, 2006, at 08:59, Pit C. wrote:

implementation it is possible to call the old one and use its
return value.

I’ve seen many different techniques in the past here on ruby-talk,
but I haven’t looked at the tradeoffs. I promise to write a page on
the ruby garden wiki with the results.

I already did this:

http://blog.segment7.net/articles/2006/03/06/attr-vs-method-vs-
define_method

You can probably match def for speed by using one of the eval methods.


Eric H. - removed_email_address@domain.invalid - http://blog.segment7.net

I LIT YOUR GEM ON FIRE!


#5

On Dec 27, 2006, at 14:26, removed_email_address@domain.invalid wrote:

did you mean to say ‘evil methods’ :wink:
There’s no need to use the evil eval:

$ parse_tree_show
class X; class_eval { def y() 5; end } end
[[:class,
:X,
[:const, :Object],
[:defn, :y, [:scope, [:block, [:args], [:lit, 5]]]]]]

Which generates the same AST as a regular def:

$ parse_tree_show
class X; def y() 5; end; end
[[:class,
:X,
[:const, :Object],
[:defn, :y, [:scope, [:block, [:args], [:lit, 5]]]]]]

If you use alias, the aliased method will be slightly slower:

$ parse_tree_show
class X; def y(); 5 end; alias x y; end
[[:class,
:X,
[:const, :Object],
[:defn, :x, [:fbody, [:scope, [:block, [:args], [:lit, 5]]]]],
[:defn, :y, [:scope, [:block, [:args], [:lit, 5]]]]]]

But you can use ruby2ruby to regenerate the aliased method without
the :fbody node (or, just inline it). I’ll leave this as an exercise
to the reader.


Eric H. - removed_email_address@domain.invalid - http://blog.segment7.net

I LIT YOUR GEM ON FIRE!


#6

On Thu, 28 Dec 2006, Eric H. wrote:

did you mean to say ‘evil methods’ :wink:

There’s no need to use the evil eval:

sorry - just trying to be punny!

-a


#7

On Thu, 28 Dec 2006, Eric H. wrote:

I already did this:

http://blog.segment7.net/articles/2006/03/06/attr-vs-method-vs-define_method

You can probably match def for speed by using one of the eval methods.

did you mean to say ‘evil methods’ :wink:

-a


#8

On Dec 27, 2006, at 16:35, Trans wrote:

http://blog.segment7.net/articles/2006/03/06/attr-vs-method-vs-
define_method

Lies.


Eric H. - removed_email_address@domain.invalid - http://blog.segment7.net

I LIT YOUR GEM ON FIRE!


#9

On Dec 27, 2006, at 7:35 PM, Trans wrote:

On Dec 27, 5:09 pm, Eric H. removed_email_address@domain.invalid wrote:

http://blog.segment7.net/articles/2006/03/06/attr-vs-method-vs-
define_method

Post Not Found…

Looks like your mail reader is inserting hard line breaks.
-Mat


#10

On Dec 27, 5:09 pm, Eric H. removed_email_address@domain.invalid wrote:

http://blog.segment7.net/articles/2006/03/06/attr-vs-method-vs-
define_method

Post Not Found…


#11

Mat S. wrote:

On Dec 27, 2006, at 7:35 PM, Trans wrote:

On Dec 27, 5:09 pm, Eric H. removed_email_address@domain.invalid wrote:

http://blog.segment7.net/articles/2006/03/06/attr-vs-method-vs-
define_method

Post Not Found…

Looks like your mail reader is inserting hard line breaks.

Interesting I’m using the google groups interface. I just noticed teh
address is split on Eric’s post, but prefectly whole on Ara’s. Strange.

T.


#12

Eric H. schrieb:

On Dec 27, 2006, at 08:59, Pit C. wrote:

I promise to write a page on the ruby garden wiki with the results.

I already did this:

http://blog.segment7.net/articles/2006/03/06/attr-vs-method-vs-define_method

Eric, thanks for the link. I’m sure these numbers will help, but they
are not exactly what I’m looking for. I’m looking for different
techniques to redefine a method in a way that still allows to call the
previous implementation. Personally, I’m mostly interested in fast
techniques, so your benchmark is very welcome.

Regards,
Pit