[英]Dynamic method calling in Ruby
據我所知,有三種方法可以在Ruby中動態調用方法:
方法1:
s = SomeObject.new
method = s.method(:dynamic_method)
method.call
方法2:
s = SomeObject.new
s.send(:dynamic_method)
方法3:
s = SomeObject.new
eval "s.dynamic_method"
通過對它們進行基准測試,我已經確定方法1是最快的,方法2是慢的,方法3是迄今為止最慢的。
我還發現, .call
和.send
都允許調用私有方法,而eval
沒有。
所以我的問題是:有沒有理由使用.send
或eval
? 為什么你不總是只使用最快的方法? 這些調用動態方法的方法有什么其他區別?
有沒有理由使用
send
?
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)
有沒有理由使用
eval
?
eval
評估任意表達式,它不僅僅用於調用方法。
關於基准測試, send
似乎比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
結果:
user system total real
send 0.210000 0.000000 0.210000 ( 0.215181)
call 0.740000 0.000000 0.740000 ( 0.739262)
想一想:
如果直接在程序上運行Ruby一次,則可以控制整個系統,並且可以通過“method.call”方法保留“指向方法的指針”。 您所做的就是抓住“實時代碼”的句柄,您可以隨時運行它。 這基本上與直接從對象內調用方法一樣快(但它沒有使用object.send那么快 - 請參閱其他答案中的基准)。
但是,如果要在數據庫中存儲要調用的方法的名稱,並且在將來的應用程序中要通過在數據庫中查找該方法名稱來調用該方法名稱,該怎么辦? 然后你將使用第二種方法,這會導致ruby使用你的第二個“s.send(:dynamic_method)”方法調用任意方法名稱。
如果您想以一種將該方法作為全新代碼運行的方式編寫/修改/保存代碼到數據庫,該怎么辦? 您可以定期修改寫入數據庫的代碼,並希望它每次都作為新代碼運行。 在這個(非常不尋常的情況)中,您可能希望使用第三種方法,它允許您將方法代碼編寫為字符串,稍后將其加載回來,並完整地運行它。
對於它的價值,通常它在Ruby世界中被認為是使用Eval(方法3)的不良形式,除非是非常非常深奧和罕見的情況。 因此,對於您遇到的幾乎所有問題,您應該堅持使用方法1和方法2。
以下是所有可能的方法調用:
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
結果是:
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
預計普通呼叫是最快的,沒有任何額外的分配,符號查找,只是查找和評估方法。
至於通過符號send
,它比通過字符串更快,因為它更容易為符號分配內存。 一旦定義它就會長期存儲在內存中,並且沒有重新分配。
關於method(:name)
可以說同樣的原因(1)它需要為Proc
對象(2)分配內存,我們在類中調用方法,這導致額外的方法查找,這也花費時間。
eval
是運行翻譯,所以它是最重的。
我從@Stefan更新了基准測試,以檢查在保存對方法的引用時是否有一些速度改進。 但是再次 - send
比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
這些是結果:
user system total real
send 0.080000 0.000000 0.080000 ( 0.088685)
call 0.110000 0.000000 0.110000 ( 0.108249)
所以send
似乎是一個采取。
send
和eval
是您可以動態更改命令。 如果要執行的方法是固定的,那么您可以在不使用send
或eval
情況下硬連接該方法。
receiver.fixed_method(argument)
但是當你想要調用一個變化的方法或者你事先不知道的方法時,你就不能直接寫出來。 因此使用send
或eval
。
receiver.send(method_that_changes_dynamically, argument)
eval "#{code_to_evaluate_that_changes_more_dramatically}"
send
附加用途是,您可以使用send
調用帶有顯式接收器的方法。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.