Ruby中的动态方法调用
据我所知,在Ruby中动态调用方法有三种方法: 方法1:Ruby中的动态方法调用,ruby,metaprogramming,dynamic-method,Ruby,Metaprogramming,Dynamic Method,据我所知,在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和.
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
需要方法对象,但不:
是否有任何理由使用eval
计算任意表达式,它不仅仅用于调用方法
关于基准测试,
发送
似乎比方法
+调用
更快:
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)
send
和eval
的全部要点是您可以动态更改命令。如果要执行的方法是固定的,则可以在不使用send
或eval
的情况下硬连接该方法
receiver.fixed_method(argument)
receiver.send(method_that_changes_dynamically, argument)
eval "#{code_to_evaluate_that_changes_more_dramatically}"
但是,当您想要调用一个变化的方法或您事先不知道的方法时,您不能直接编写它。因此使用了send
或eval
receiver.fixed_method(argument)
receiver.send(method_that_changes_dynamically, argument)
eval "#{code_to_evaluate_that_changes_more_dramatically}"
send
的另一个用途是,正如您所注意到的,您可以使用send
调用带有显式接收器的方法,您可以这样想:
方法1(Method.call):单次运行时
如果您在程序上直接运行Ruby一次,您就可以控制整个系统,并且可以通过“method.call”方法保持“指向您的方法的指针”。您所要做的就是保持“实时代码”的句柄,以便随时运行。这基本上与直接从对象内部调用方法一样快(但不如使用object.send那么快-请参阅其他答案中的基准测试)
方法2(object.send):将方法的名称持久化到数据库
但是,如果要在数据库中存储要调用的方法的名称,并且在将来的应用程序中要通过在数据库中查找来调用该方法的名称,该怎么办?然后使用第二种方法,这会导致ruby使用第二种“s.send(:dynamic_method)”方法调用任意方法名
方法3(eval):自修改方法代码
如果您希望以将该方法作为全新代码运行的方式向数据库写入/修改/持久化代码,该怎么办?您可能会定期修改写入数据库的代码,并希望每次都以新代码的形式运行。在这种情况下(非常罕见的情况),您可能希望使用第三种方法,它允许您将方法代码作为字符串写出,在稍后的某个日期重新加载,然后完整地运行它
无论如何,在Ruby世界中,使用Eval(方法3)通常被视为不好的形式,除非是在非常、非常深奥和罕见的情况下。因此,对于几乎所有遇到的问题,您都应该坚持使用方法1和方法2。我从@Stefan更新了基准测试,以检查在保存对方法的引用时是否有一些速度改进。但是再次强调–
发送
比呼叫
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
via symbol,它比via string更快,因为它更容易为symbol分配内存。一旦它被定义,它将长期存储在内存中,并且没有重新分配
关于方法(:name)
(1)需要为Proc
对象分配内存(2)我们在类中调用该方法,这会导致额外的方法查找,这也需要时间
eval
是运行解释器的,所以它是最重的。好的建设性问题:)+1..@Abraham+1好问题!您可能希望在方法2代码示例中包含基准测试结果并修复键入错误。“我还发现.call和.send都允许调用私有方法,而eval则不允许。”您可以使用.public\u send
而不是。send
来禁止调用私有方法。感谢您深思熟虑且信息丰富的回答,我投了更高的票。我在下面给Stefan一个公认的答案,包括一组基准测试,提出方法_missing,并首先响应。谢谢你!这是一个更准确的基准。谢谢你的发帖。