私の知る限り、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
は許可しません。
私の質問は次のとおりです。.send
またはeval
?常に最速の方法を常に使用するとは限らないのはなぜですか?動的メソッドを呼び出すこれらのメソッドには、他にどのような違いがありますか?
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)
このように考えてください:
プログラムでRubyを1回実行すると、システム全体を制御し、「method.call」アプローチを介して「メソッドへのポインタ」を保持できます。これは、基本的にオブジェクト内から直接メソッドを呼び出すのと同じくらい高速です(ただし、object.sendを使用するほど高速ではありません-他のベンチマークを参照してください)回答)。
しかし、呼び出したいメソッドの名前をデータベースに保存し、将来のアプリケーションでデータベースで検索してそのメソッド名を呼び出したい場合はどうでしょうか?次に、2番目のアプローチを使用します。これにより、Rubyは2番目の「s.send(:dynamic_method)」アプローチを使用して任意のメソッド名を呼び出します。
メソッドをまったく新しいコードとして実行する方法で、データベースにコードを書き込み/変更/永続化したい場合はどうでしょうか?データベースに書き込まれたコードを定期的に変更し、毎回新しいコードとして実行したい場合があります。この場合(非常にまれなケース)、3番目のアプローチを使用します。これにより、メソッドコードを文字列として記述し、後日ロードし直して、完全に実行できます。
価値のあるものとしては、一般的にRuby世界ではEval(方法3)を使用するのは悪い形式であると見なされます。そして、あなたが遭遇するほとんどすべての問題のための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
を使用して明示的なレシーバーでメソッドを呼び出すことができるということです。