今天刚巧看到Matt Aimonetti的blog上讨论ruby meta programming的执行效率问题,就跟着做了一下,还是学到了不少东西。大致说一下吧,首先Matt写了一个简单的计算时间的DSL类:
module TimeDSL
def second
self * 1
end
alias_method :seconds, :second
def minute
self * 60
end
alias_method :minutes, :minute
def hour
self * 3600
end
alias_method :hours, :hour
def day
self * 86400
end
alias_method :days, :day
def week
self * 604800
end
alias_method :weeks, :week
def month
self * 2592000
end
alias_method :months, :month
def year
self * 31471200
end
alias_method :years, :year
end
Numeric.send :include, TimeDSL
这个类很简单,就是为Numeric类增加了与时间相关的一些方法,这样就可以使用类似的代码计算时间了,例如:
1.hours + 20.minutes # => 一小时20分钟是多少秒
3.days + 5.hours # => 3天零5小时
2.years + 10.months # => 两年十个月
代码很简单,不过请大家注意Matt使用
Numeric.send :include, TimeDSL
方法为Numeric类增加了TimeDSL这个模块。
然后他又写了一个利用define_method方式实现TimeDSL的类(为了方便说明,我略作了修改)
module BadMetaTimeDSL
{:second => 1,
:minute => 60,
:hour => 3600,
:day => [24,:hours],
:week => [7,:days],
:month => [30,:days],
:year => [364.25, :days]}.each do |meth, amount|
define_method "b_#{meth}" do
amount = amount.is_a?(Array) ? amount[0].send(amount[1]) : amount
self * amount
end
alias_method "b_#{meth}s".intern, "b_#{meth}"
end
end
Numeric.send :include, BadMetaTimeDSL
他发布了这两个类benchmark的测试数据,并得出结论说ruby的meta programming基本上要比正常方式慢3倍左右(我自己的测试数据是2倍左右)
这个结论很快就引来了很多人的的讨论,最后Wycats提出了改进方案
module GoodMetaTimeDSL
SECOND = 1
MINUTE = SECOND * 60
HOUR = MINUTE * 60
DAY = HOUR * 24
WEEK = DAY * 7
MONTH = DAY * 30
YEAR = DAY * 364.25
%w[SECOND MINUTE HOUR DAY WEEK MONTH YEAR].each do |const_name|
meth = const_name.downcase
class_eval <<-RUBY
def g_#{meth}
self * #{const_name}
end
alias g_#{meth}s g_#{meth}
RUBY
end
end
Numeric.send :include, GoodMetaTimeDSL
新的类执行效率直逼正常方式定义的类,可以说基本上没有太大的差异了。究其原因define_method需要创建Proc才能执行,而class_eval和ruby自身执行方式没有什么差别,都是直接解释执行。
最后的结论是:如果可能,尽量使用class_eval进行meta programming
不知道怎么把附件的图贴进来,大家看附件吧,也包括源代码。
整个测试都在jruby 1.1.3下通过
可以参考
http://railsontherun.com/2008/6/18/about-metaprogramming-speed