71

私の知る限り、Ruby でメソッドを動的に呼び出す方法は 3 つあります。

方法 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? 常に最速の方法を使用しないのはなぜですか? 動的メソッドを呼び出すこれらのメソッドには、他にどのような違いがありますか?

4

5 に答える 5

67

使用する理由はあります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よりも速いようです:methodcall

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)
于 2013-07-03T18:14:52.097 に答える
12

次のように考えてください。

方法 1 (method.call): シングル ランタイム

プログラムで一度Rubyを実行すると、システム全体を制御し、「method.call」アプローチを介して「メソッドへのポインター」を保持できます。あなたがしているのは、いつでも実行できる「ライブ コード」へのハンドルを保持しているだけです。これは基本的に、オブジェクト内からメソッドを直接呼び出すのと同じくらい高速です (ただし、object.send を使用するほど高速ではありません - 他の回答のベンチマークを参照してください)。

方法 2 (object.send): メソッドの名前をデータベースに永続化する

しかし、呼び出したいメソッドの名前をデータベースに保存しておき、将来のアプリケーションでそのメソッド名をデータベースで検索して呼び出す場合はどうすればよいでしょうか? 次に、2 番目のアプローチを使用します。これにより、2 番目の「s.send(:dynamic_method)」アプローチを使用して、ruby が任意のメソッド名を呼び出すようになります。

メソッド 3 (eval): 自己変更メソッド コード

メソッドを新しいコードとして実行する方法で、コードをデータベースに記述/変更/永続化したい場合はどうすればよいでしょうか? データベースに書き込まれたコードを定期的に変更し、毎回新しいコードとして実行したい場合があります。この場合 (非常にまれなケース) は、メソッド コードを文字列として書き出すことができる 3 番目のアプローチを使用することをお勧めします。

Ruby の世界では、非常に難解でまれな場合を除いて、Eval (方法 3) を使用するのは一般的に悪い形式と見なされています。したがって、遭遇するほとんどすべての問題に対して、方法 1 と 2 を使用する必要があります。

于 2013-07-04T03:57:21.190 に答える
5

可能なすべてのメソッド呼び出しは次のとおりです。

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インタープリターを実行するため、最も重いです。

于 2017-06-01T20:33:56.103 に答える
3

メソッドへの参照を保存するときに速度が向上するかどうかを確認するために、@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取るものと思われる。

于 2015-03-18T21:25:48.680 に答える
0

sendandの要点は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

于 2013-07-03T18:40:30.933 に答える