可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
As far as I am aware 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"
By benchmarking them I have established that Method 1 is by far the fastest, Method 2 is slower, and Method 3 is by far the slowest.
I have also found that .call
and .send
both allow calling private methods, while eval
does not.
So my question is: is there any reason to ever use .send
or eval
? Why would you not always just use the fastest method? What other differences do these methods of calling dynamic methods have?
回答1:
is there any reason to ever use send
?
call
needs a method object, send
doesn't:
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, it's not just for calling a method.
Regarding benchmarks, send
seems to be 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)
回答2:
Think of it this way:
Method 1 (method.call): Single run-time
If you run Ruby once on your program straight through, you control the entire system and you can hold onto a "pointer to your method" via the "method.call" approach. All you are doing is holding on to a handle to "live code" that you can run whenever you want. This is basically as fast as calling the method directly from within the object (but it is not as fast as using object.send - see benchmarks in other answers).
Method 2 (object.send): Persist name of method to database
But what if you want to store the name of the method you want to call in a database and in a future application you want to call that method name by looking it up in the database? Then you would use the second approach, which causes ruby to call an arbitrary method name using your second "s.send(:dynamic_method)" approach.
Method 3 (eval): Self-modifying method code
What if you want to write/modify/persist code to a database in a way that will run the method as brand new code? You might periodically modify the code written to the database and want it to run as new code each time. In this (very unusual case) you would want to use your third approach, which allows you to write your method code out as a string, load it back in at some later date, and run it in its entirety.
For what it's worth, generally it is regarded in the Ruby world as bad form to use Eval (method 3) except in very, very esoteric and rare cases. So you should really stick with methods 1 and 2 for almost all problems you encounter.
回答3:
I updated the benchmark from @Stefan to check if there are some speed improvements when saving reference to method. But again – send
is 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
These 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 the one to take.
回答4:
Here is all 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 results are:
Warming up --------------------------------------
plain 236.448k i/100ms
eval 20.743k i/100ms
method call 131.408k i/100ms
send sym 205.491k i/100ms
send str 168.137k i/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's expected that plain call is the fastest, no any additional allocations, symbol lookups, just lookup and evaluation of method.
As for send
via symbol, it is faster than via string as its much more easer to allocate memory for symbol. Once it's defined it's stored for long term in memory and there no reallocations.
The same reason can be said about method(:name)
(1) it's requires to allocate memory for Proc
object (2) we are calling the method in class which leads for additional method lookup which takes time too.
eval
is runs interpreter so it's the heaviest.
回答5:
The whole point of send
and eval
is that you can change the command dynamically. If the method you want to execute is fixed, then you can hard-wire that method without using send
or eval
.
receiver.fixed_method(argument)
But when you want to invoke a method that varies or you do not know in advance, then you cannot write that directly. Hence the use of send
or eval
.
receiver.send(method_that_changes_dynamically, argument)
eval "#{code_to_evaluate_that_changes_more_dramatically}"
Additional use of send
is that, as you noticed, you can call a method with explicit receiver using send
.