做了一个 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 才能执行,不明白为什么,求解释。 补充:我问的是代码的执行效率,而不是代码的加载效率。
是啊,感觉好奇怪,不知道是不是花在方法查找上去了 不过创建对象的时间到时一样的
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)
#6 楼 @zzhattzzh send :define_method 这个过程是在类载入的时候进行的(类载入的时候为这个类添加一个 hello 的实例方法),后续对 hello 的所有调用和直接写 def 的做法并无差别
顺便罗嗦一句,ruby 的很多元编程能力很多只是在 runtime 时变化了类的固有行为逻辑,但是由于逻辑本身是固化的,调用栈的深度没有增加,此时语言的动态性并不会降低性能。所以如果你不是用 method_missing 这一类技巧,那么无需担心运行效率
#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
首先可以肯定,性能的损失来自于调用 define_method 方法 block, 如果没有这个 block 的调用,它们所花的时间一定是一样的。
一个是关键字直接定义方法,方法名作为原义被直接定义。
另一个是方法调用,而且需要通过符号获取方法名,最后内部还要完成类似于 使用关键字定义一个方法
的过程,多了一道流程,速度慢一些,但是允许通过符号动态的生成方法名称,有得有失。