簡體   English   中英

在Ruby中調用動態方法

[英]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沒有。

所以我的問題是:有沒有理由使用.sendeval 為什么你不總是只使用最快的方法? 這些調用動態方法的方法有什么其他區別?

有沒有理由使用send

call需要一個方法對象, 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)

想一想:

方法1(method.call):單個運行時

如果直接在程序上運行Ruby一次,則可以控制整個系統,並且可以通過“method.call”方法保留“指向方法的指針”。 您所做的就是抓住“實時代碼”的句柄,您可以隨時運行它。 這基本上與直接從對象內調用方法一樣快(但它沒有使用object.send那么快 - 請參閱其他答案中的基准)。

方法2(object.send):持久化數據庫的方法名稱

但是,如果要在數據庫中存儲要調用的方法的名稱,並且在將來的應用程序中要通過在數據庫中查找該方法名稱來調用該方法名稱,該怎么辦? 然后你將使用第二種方法,這會導致ruby使用你的第二個“s.send(:dynamic_method)”方法調用任意方法名稱。

方法3(eval):自修改方法代碼

如果您想以一種將該方法作為全新代碼運行的方式編寫/修改/保存代碼到數據庫,該怎么辦? 您可以定期修改寫入數據庫的代碼,並希望它每次都作為新代碼運行。 在這個(非常不尋常的情況)中,您可能希望使用第三種方法,它允許您將方法代碼編寫為字符串,稍后將其加載回來,並完整地運行它。

對於它的價值,通常它在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更新了基准測試,以檢查在保存對方法的引用時是否有一些速度改進。 但是再次 - sendcall快得多

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似乎是一個采取。

sendeval是您可以動態更改命令。 如果要執行的方法是固定的,那么您可以在不使用sendeval情況下硬連接該方法。

receiver.fixed_method(argument)

但是當你想要調用一個變化的方法或者你事先不知道的方法時,你就不能直接寫出來。 因此使用sendeval

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM