Динамический вызов метода в 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
? Почему бы вам не всегда использовать самый быстрый метод? Какие еще отличия имеют эти методы вызова динамических методов?
5 ответов:
есть ли причина когда-либо использовать
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 (метод.call): Single run-time
Если вы запустите Ruby один раз в своей программе прямо, вы контролируете всю систему, и вы можете удерживать "указатель на свой метод" через "метод.вызов " подход. Все, что вы делаете, это держитесь за ручку "живого кода", который вы можете запускать, когда захотите. Это в основном так же быстро, как вызов метода непосредственно из объекта (но это не так быстро, как использование объекта.отправить-посмотреть ориентиры в других ответах).
Способ 2 (Объект.send): сохранение имени метода в базе данных
но что делать, если вы хотите сохранить имя метода, который вы хотите вызвать в базе данных, и в будущем приложении вы хотите вызвать это имя метода, просмотрев его в базе данных? Затем вы должны использовать второй подход, который заставляет ruby вызывать произвольное имя метода, используя ваш второй подход "s.send(:dynamic_method)".
Метод 3 (eval): Самомодифицирующийся код метода
Что делать, если вы хотите написать/изменить/сохранить код в базу данных таким образом, что будет работать метод как новый код? Вы можете периодически изменять код, записанный в базу данных, и каждый раз запускать его как новый код. В этом (очень необычном случае) вы хотели бы использовать свой третий подход, который позволяет вам написать свой код метода в виде строки, загрузить его обратно в какой-то более поздний срок и запустить его полностью.
для чего это стоит, как правило, это в рубиновом мире считается дурным тоном использовать Eval (метод 3) за исключением очень, очень эзотерических и редких случаев. Таким образом, вы должны действительно придерживаться методов 1 и 2 для почти всех проблем, с которыми вы сталкиваетесь.
я обновил тест от @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
Кажется, тот, чтобы взять.
вот все возможные вызовы метода:
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
object (2) мы вызываем метод в классе, который приводит к дополнительному поиску метода, который также требует времени.
eval
работает переводчик, так что это самый тяжелый.
смысл
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
.