Dynamic call method in Ruby

As far as I know, there are three ways to dynamically call a method in Ruby:

Method 1:

s = SomeObject.new method = s.method(:dynamic_method) method.call 

Method 2:

 s = SomeObject.new s.send(:dynamic_method) 

Method 3:

 s = SomeObject.new eval "s.dynamic_method" 

Comparing them, I found that method 1 is by far the fastest, method 2 is slower, and method 3 is by far the slowest.

I also found that .call and .send allow private methods, while eval does not.

So my question is: is there any reason to ever use .send or eval ? Why don't you always use the fastest method? What other differences do these dynamic method invocation methods have?

+65
ruby metaprogramming
Jul 03 '13 at 17:54
source share
5 answers

Is there any reason to ever use send ?

call requires a method object, send does not:

 class Foo def method_missing(name) "#{name} called" end end Foo.new.send(:bar) #=> "bar called" Foo.new.method(:bar).call #=> undefined method `bar' for class `Foo' (NameError) 

Is there any reason to ever use eval ?

eval evaluates arbitrary expressions; this is not just for calling a method.




As for the tests, send seems faster than method + call :

 require 'benchmark' class Foo def bar; end end Benchmark.bm(4) do |b| b.report("send") { 1_000_000.times { Foo.new.send(:bar) } } b.report("call") { 1_000_000.times { Foo.new.method(:bar).call } } end 

Result:

  user system total real send 0.210000 0.000000 0.210000 ( 0.215181) call 0.740000 0.000000 0.740000 ( 0.739262) 
+58
Jul 03 '13 at 18:14
source share

Think of it this way:

Method 1 (.call method): single runtime

If you run Ruby once in your program directly, you control the entire system, and you can hold the "pointer to your method" using the method.call method. All you do is hold the "live code" knob, which you can run whenever you want. This is basically as fast as calling a method directly from an object (but it is not as fast as using object.send - see Tests in other answers).

Method 2 (object.send): save the method name in the database

But what if you want to keep the name of the method that you want to call in the database, and in a future application that you want to call this method name by looking at it in the database? Then you will use the second approach, which causes ruby ​​to call an arbitrary method name, using your second approach, "s.send (: dynamic_method)".

Method 3 (eval): self-modification method code

What if you want to write / modify / transfer the code to the database in such a way as to run this method as new code? You can periodically change the code recorded in the database, and each time run it as new. In this (very unusual case), you would like to use your third approach, which allows you to write the method code as a string, load it at some later date and run it in its entirety.

Why is it worth, as a rule, in the world of Ruby it is considered as a bad form of using Eval (method 3), except in very, very esoteric and rare cases. Therefore, you should adhere to methods 1 and 2 for almost all the problems that you encounter.

+12
Jul 04 '13 at 3:57 on
source share

I updated the benchmark from @Stefan to check if there are some speed improvements while keeping the method reference. But again - send much faster than call

 require 'benchmark' class Foo def bar; end end foo = Foo.new foo_bar = foo.method(:bar) Benchmark.bm(4) do |b| b.report("send") { 1_000_000.times { foo.send(:bar) } } b.report("call") { 1_000_000.times { foo_bar.call } } end 

Here are the results:

  user system total real send 0.080000 0.000000 0.080000 ( 0.088685) call 0.110000 0.000000 0.110000 ( 0.108249) 

So send seems to be what needs to be done.

+3
Mar 18 '15 at 21:25
source share

Here are all the possible method calls:

 require 'benchmark/ips' class FooBar def name; end end el = FooBar.new Benchmark.ips do |x| x.report('plain') { el.name } x.report('eval') { eval('el.name') } x.report('method call') { el.method(:name).call } x.report('send sym') { el.send(:name) } x.report('send str') { el.send('name') } x.compare! end 

And the results:

 Warming up -------------------------------------- plain 236.448ki/100ms eval 20.743ki/100ms method call 131.408ki/100ms send sym 205.491ki/100ms send str 168.137ki/100ms Calculating ------------------------------------- plain 9.150M (Β± 6.5%) i/s - 45.634M in 5.009566s eval 232.303k (Β± 5.4%) i/s - 1.162M in 5.015430s method call 2.602M (Β± 4.5%) i/s - 13.009M in 5.010535s send sym 6.729M (Β± 8.6%) i/s - 33.495M in 5.016481s send str 4.027M (Β± 5.7%) i/s - 20.176M in 5.027409s Comparison: plain: 9149514.0 i/s send sym: 6729490.1 i/s - 1.36x slower send str: 4026672.4 i/s - 2.27x slower method call: 2601777.5 i/s - 3.52x slower eval: 232302.6 i/s - 39.39x slower 

It was expected that a simple call would be the fastest, no additional allocations, search by characters, just searching and evaluating the method.

As for the send character through the character, it is faster than through the string, since it is much easier to allocate memory for the character. After he determined that he has been kept in memory for a long time, and there is no redistribution.

The same can be said about method(:name) (1), in order to allocate memory for the Proc (2) object, we call the method in the class, which leads to an additional search for the method, which also takes time.

eval launches the interpreter, so it is the heaviest.

+1
Jun 01 '17 at 20:33 on
source share

The whole point of send and eval is that you can change the command dynamically. If the method you want to execute is fixed, you can rigidly bind this method without using send or eval .

 receiver.fixed_method(argument) 

But if you want to call a method that changes or you do not know in advance, then you cannot write it directly. Hence the use of send or eval .

 receiver.send(method_that_changes_dynamically, argument) eval "#{code_to_evaluate_that_changes_more_dramatically}" 

An additional use of send is that, as you noticed, you can invoke a method with an explicit receiver using send .

0
Jul 03 '13 at 18:40
source share



All Articles