Ruby ruby define_method 为什么比直接定义方法要慢?

zuozuo · 发布于 2012年10月18日 · 最后由 zw963 回复于 2012年10月22日 · 2838 次阅读
1085

做了一个benchmark:

class MyClass
  def hello
    "hello"
  end
end
Benchmark.measure { 1000000.times {MyClass.new.sb} }
#=>   0.450000   0.000000   0.450000 (  0.446404)

class MyClass
  send :define_method,"hello" do
    "hello"
  end
end
#=>   0.560000   0.000000   0.560000 (  0.564162)

这是为什么呢? 网上有帖子说是因为define_method需要创建Proc才能执行,不明白为什么,求解释。 补充:我问的是代码的执行效率,而不是代码的加载效率。

共收到 14 条回复
2622

原因应该很复杂吧,调用方法也需要时间

1085

#1楼 @jjym 两种方法都需要调用方法呀,而且都是只在执行的时候调用一次,因为即使是define_method的话,在代码加载时方法就已经是定义好了的。

17

是啊,感觉好奇怪,不知道是不是花在方法查找上去了 不过创建对象的时间到时一样的

require 'benchmark'

class Mimmy
  def hi
    'mimmy'
  end
end

class Kitty
  def self.define_hi
    puts 'hi kitty'
    define_method :hi do
      'kitty'
    end
  end

  define_hi
end

3.times do
  puts Benchmark.measure { 1000000.times {Kitty.new} }
  puts Benchmark.measure { 1000000.times {Mimmy.new} }
end

puts "*"*30

3.times do
  puts Benchmark.measure { 1000000.times {Kitty.new.hi} }
  puts Benchmark.measure { 1000000.times {Mimmy.new.hi} }
end

运行结果

hi kitty
  0.580000   0.000000   0.580000 (  0.586829)
  0.480000   0.000000   0.480000 (  0.497991)
  0.490000   0.000000   0.490000 (  0.497683)
  0.490000   0.000000   0.490000 (  0.501469)
  0.490000   0.000000   0.490000 (  0.505061)
  0.500000   0.000000   0.500000 (  0.504393)
******************************
  0.490000   0.000000   0.490000 (  0.497051)
  0.600000   0.000000   0.600000 (  0.657138)
  0.500000   0.000000   0.500000 (  0.522111)
  0.490000   0.000000   0.490000 (  0.510200)
  0.490000   0.000000   0.490000 (  0.512266)
  0.490000   0.000000   0.490000 (  0.506171)
[outman@outman-desktop tmp]$ ruby kitty.rb 
hi kitty
  0.590000   0.000000   0.590000 (  0.628322)
  0.490000   0.000000   0.490000 (  0.508545)
  0.500000   0.000000   0.500000 (  0.507284)
  0.490000   0.000000   0.490000 (  0.505821)
  0.490000   0.000000   0.490000 (  0.519993)
  0.530000   0.000000   0.530000 (  0.571263)
******************************
  1.520000   0.000000   1.520000 (  1.554227)
  0.780000   0.000000   0.780000 (  0.807944)
  1.500000   0.010000   1.510000 (  1.544104)
  0.800000   0.000000   0.800000 (  0.813888)
  1.300000   0.000000   1.300000 (  1.322703)
  0.790000   0.000000   0.790000 (  0.802711)
681

多调用了一次 define_method 方法和 send 方法。

244

#4楼 @sevk 正解 对象创建的时间一样是因为本来这两种方式产生的结果就是基本等价的

1085

#4楼 @sevk @fsword 我不太明白的地方是:define_method 和 send 方法是在代码加载的时候调用的还是代码执行的时候(调用 hello 方法)调用?

2622

#6楼 @zzhattzzh .....难道你认为每调用次hello就会多个方法吗

244

#6楼 @zzhattzzh send :define_method 这个过程是在类载入的时候进行的(类载入的时候为这个类添加一个hello的实例方法),后续对hello的所有调用和直接写 def 的做法并无差别

244

顺便罗嗦一句,ruby的很多元编程能力很多只是在runtime时变化了类的固有行为逻辑,但是由于逻辑本身是固化的,调用栈的深度没有增加,此时语言的动态性并不会降低性能。所以如果你不是用method_missing这一类技巧,那么无需担心运行效率

1085

#7楼 @jjym #8楼 @fsword 我也是这么想的,按道理说程序加载的时候 send 和 define_method 已经执行过了,在程序执行的时候 define_method 和直接用 def 的效果应该是一样的,所以就更不明白为什么 defind_method 会慢了。

2622

有可能是define_method是用lambda来保存的,所以还要调用call花掉时间??

等看过ruby实现的大牛来解答吧。。

244

#10楼 @zzhattzzh 帖子修改过了?一开始好像说的不是这个意思?

如果真要看执行时的差异,可以这样

1.9.3p194 :038 > iseq = ISeq.compile "class MyClass2; def hello; 'hello' end; end"
 => <RubyVM::InstructionSequence:<compiled>@<compiled>> 
1.9.3p194 :039 > puts iseq.disassemble
== disasm: <RubyVM::InstructionSequence:<compiled>@<compiled>>==========
0000 trace            1                                               (   1)
0002 putspecialobject 3
0004 putnil           
0005 defineclass      :MyClass2, <class:MyClass2>, 3
0009 leave            
== disasm: <RubyVM::InstructionSequence:<class:MyClass2>@<compiled>>====
0000 trace            2                                               (   1)
0002 trace            1
0004 putspecialobject 1
0006 putspecialobject 2
0008 putobject        :hello
0010 putiseq          hello
0012 send             :"core#define_method", 3, nil, 0, <ic:0>
0018 trace            4
0020 leave            
== disasm: <RubyVM::InstructionSequence:hello@<compiled>>===============
0000 trace            8                                               (   1)
0002 trace            1
0004 putstring        "hello"
0006 trace            16
0008 leave            
 => nil 
1.9.3p194 :040 > iseq = ISeq.compile "MyClass2.send :define_method, 'hello' do 'hello' end"
 => <RubyVM::InstructionSequence:<compiled>@<compiled>> 
1.9.3p194 :041 > puts iseq.disassemble
== disasm: <RubyVM::InstructionSequence:<compiled>@<compiled>>==========
== catch table
| catch type: break  st: 0002 ed: 0019 sp: 0000 cont: 0019
|------------------------------------------------------------------------
0000 trace            1                                               (   1)
0002 getinlinecache   9, <ic:0>
0005 getconstant      :MyClass2
0007 setinlinecache   <ic:0>
0009 putobject        :define_method
0011 putstring        "hello"
0013 send             :send, 2, block in <compiled>, 0, <ic:1>
0019 leave            
== disasm: <RubyVM::InstructionSequence:block in <compiled>@<compiled>>=
== catch table
| catch type: redo   st: 0000 ed: 0004 sp: 0000 cont: 0000
| catch type: next   st: 0000 ed: 0004 sp: 0000 cont: 0004
|------------------------------------------------------------------------
0000 trace            1                                               (   1)
0002 putstring        "hello"
0004 leave            
 => nil 

似乎是用define_method添加的方法会把block封装成调用对象,于是每次调用时,调用栈会多一层 之前的说法不是很准确,稍微精确的说法应该是define_method等价于下面的代码

class MyClass3
  def hello
    inner_proc
  end

  def inner_proc
    'hello'
  end
end
1085

#12楼 @fsword 帖子没改过,不过你说的很有道理,受教了。

1031

首先可以肯定, 性能的损失来自于调用 define_method 方法 block, 如果没有这个 block 的调用, 它们所花的时间一定是一样的.

一个是关键字直接定义方法, 方法名作为原义被直接定义. 另一个是方法调用, 而且需要通过符号获取方法名, 最后内部还要完成类似于 使用关键字定义一个方法 的过程, 多了一道流程, 速度慢一些, 但是允许通过符号动态的生成方法名称, 有得有失.

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册