首页»RubyOnRails»我是如何让 Ruby 项目提升 10 倍速度的

我是如何让 Ruby 项目提升 10 倍速度的

来源:oschina 发布时间:2013-09-03 阅读次数:

  这篇文章主要介绍了我是如何把ruby gem contracts.ruby速度提升10倍的。

  contracts.ruby是我的一个项目,它用来为Ruby增加一些代码合约。它看起来像这样:

Contract Num, Num => Num
def add(a, b)
  a + b
end

  现在,只要add被调用,其参数与返回值都将会被检查。酷!

 20 秒

  本周末我校验了这个库,发现它的性能非常糟糕。

                                     user     system      total        real
testing add                      0.510000   0.000000   0.510000 (  0.509791)
testing contracts add           20.630000   0.040000  20.670000 ( 20.726758)

  这是在随机输入时,运行两个函数1,000,000次以后的结果。

  所以给一个函数增加合约最终将引起极大的(40倍)降速。我开始探究其中的原因。

 8 秒

  我立刻就获得了一个极大的进展。当一个合约传递的时候,我调用了一个名为success_callback的函数。这个函数是完全空的。这是它的完整定义:

def self.success_callback(data)
end  

  这是我归结为“仅仅是案例”(未来再验证!)的一类。原来,函数调用在Ruby中代价十分昂贵。仅仅删除它就节约了8秒钟!

                                     user     system      total        real
testing add                      0.520000   0.000000   0.520000 (  0.517302)
testing contracts add           12.120000   0.010000  12.130000 ( 12.140564)

  删除许多其他附加的函数调用,我有了9.84-> 9.59-> 8.01秒的结果。这个库已经超过原来两倍速了!

  现在问题开始有点更为复杂了。

 5.93 秒

  有多种方法来定义一个合约:匿名(lambdas),类 (classes), 简单旧数据(plain ol’ values), 等等。我有个很长的case语句,用来检测它是什么类型的合约。在此合约类型基础之上,我可以做不同的事情。通过把它改为if语句,我节约了一些时间,但每次这个函数调用时,我仍然耗费了不必要的时间在穿越这个判定树上面:

if contract.is_a?(Class)
  # check arg
elsif contract.is_a?(Hash)
  # check arg
...

  我将其修改为合约定义的时候,以及创建lambdas的时候,只需一次穿越树:

if contract.is_a?(Class)
  lambda { |arg| # check arg }
elsif contract.is_a?(Hash)
  lambda { |arg| # check arg }
...

  之后我通过将参数传递给这个预计算的lambda来进行校验,完全绕过了逻辑分支。这又节约了1.2秒。

                                     user     system      total        real
testing add                      0.510000   0.000000   0.510000 (  0.516848)
testing contracts add            6.780000   0.000000   6.780000 (  6.785446)

  预计算一些其它的if语句几乎又节约1秒钟:

                                     user     system      total        real
testing add                      0.510000   0.000000   0.510000 (  0.516527)
testing contracts add            5.930000   0.000000   5.930000 (  5.933225)

 5.09 秒

  断开.zip的.times为我几乎又节约了一秒钟:

                                     user     system      total        real
testing add                      0.510000   0.000000   0.510000 (  0.507554)
testing contracts add            5.090000   0.010000   5.100000 (  5.099530)

  原来,

args.zip(contracts).each do |arg, contract|

  要比

args.each_with_index do |arg, i|

  更慢,而后者又比

 args.size.times do |i|

  更慢。

  .zip耗费了不必要的时间来拷贝与创建一个新的数组。我想.each_with_index之所以更慢,是因为它受制于背后的.each,所以它涉及到两个限制而不是一个。

 4.23 秒

  现在我们看一些细节的东西。contracts库工作的方式是这样的,对每个方法增加一个使用class_eval的新方法(class_eval比define_method快)。这个新方法中包含了一个到旧方法的引用。当新方法被调用时,它检查参数,然后使用这些参数调用老方法,然后检查返回值,最后返回返回值。所有这些调用contractclass:check_args和check_result两个方法。我去除了这两个方法的调用,在新方法中检查是否正确。这样我又节省了0.9秒:

                                     user     system      total        real
testing add                      0.530000   0.000000   0.530000 (  0.523503)
testing contracts add            4.230000   0.000000   4.230000 (  4.244071)

 2.94 秒

  之前我曾经解释过,我是怎样在合约类型基础之上创建lambdas,之后再用它们来检测参数。我换了一种方法,用生成代码来替代,当我用class_eval来创建新的方法时,它就会从eval获得结果。一个糟糕的漏洞!但它避免了一大堆方法调用,并且为我又节省了1.25秒。

                                     user     system      total        real
testing add                      0.520000   0.000000   0.520000 (  0.519425)
testing contracts add            2.940000   0.000000   2.940000 (  2.942372)

 1.57秒

  最后,我改变了调用重写方法的方式。我之前的方法是使用一个引用:

# simplification
old_method = method(name)

class_eval %{
    def #{name}(*args)
        old_method.bind(self).call(*args)
    end
}

  我把方法调用改成了 alias_method的方式:

alias_method :"original_#{name}", name
class_eval %{
    def #{name}(*args)
        self.send(:"original_#{name}", *args)
      end
}

  这带给了我1.4秒的惊喜。我不知道为什么 alias_method is这么快...我猜测可能是因为跳过了方法调用和绑定

                                     user     system      total        real
testing add                      0.520000   0.000000   0.520000 (  0.518431)
testing contracts add            1.570000   0.000000   1.570000 (  1.568863)

 结果

  我们设计是从20秒到1.5秒!是否可能做得比这更好呢?我不这么认为。我写的这个测试脚本表明,一个包裹的添加方法将比定期添加方法慢3倍,所以这些数字已经很好了。

  方法很简单,更多的时间花在调用方法是只慢3倍的原因。这是一个更现实的例子:一个函数读文件100000次:

                                     user     system      total        real
testing read                     1.200000   1.330000   2.530000 (  2.521314)
testing contracts read           1.530000   1.370000   2.900000 (  2.903721)

 慢了很小一点!我认为大多数函数只能看到稍慢一点,addfunction是个例外。

 我决定不使用alias_method,因为它污染命名空间而且那些别名函数会到处出现(文档,IDE的自动完成等)。

 一些额外的:

  1. Ruby中方法调用很慢,我喜欢将我的代码模块化的和重复使用,但也许是我开始内联代码的时候了。
  2. 测试你的代码!删掉一个简单的未使用的方法花费我20秒到12秒。

 其他尝试的东西

  方法选择器

  Ruby2.0没有引入的一个特性是方法选择器,这运行你这样写

class Foo
  def bar:before
    # will always run before bar, when bar is called
  end

  def bar:after
    # will always run after bar, when bar is called
    # may or may not be able to access and/or change bar's return value
  end
end

  这使写装饰器更容易,而且可能更快。

  keywordold

  Ruby2.0没有引入的另一个特性,这允许你引用一个重写方法:

class Foo
  def bar
    'Hello'
  end
end 

class Foo
  def bar
    old + ' World'
  end
end

Foo.new.bar # => 'Hello World'

  使用redef重新定义方法

  这个Matz说过:

To eliminatealias_method_chain, we introducedModule#prepend. There’s no chance to add redundant feature in the language.

  所以如果redef是冗余的特征,也许prepend可以用来写修饰器了?

  其他的实现

  到目前为止,所有这一切都已经在YARV上测试过。也许Rubinius会让我做更加优化?

 参考

  原文地址:http://www.adit.io/posts/2013-03-04-How-I-Made-My-Ruby-Project-10x-Faster.html

QQ群:凯发娱乐官网官方群(515171538),验证消息:10000
微信群:加小编微信 849023636 邀请您加入,验证消息:10000
提示:更多精彩内容关注微信公众号:全栈开发者中心(fsder-com)
m88 188bet uedbet 威廉希尔 明升 bwin 明升88 bodog bwin 明升m88.com 18luck 188bet unibet unibet Ladbrokes Ladbrokes 真钱的棋牌游戏 casino m88明升 明升 明升 m88.com 188bet m88 明陞 uedbet赫塔菲官网 365bet官网 nba英文官网 嘉年华官网 皇冠备用 火箭队官网 中国人论坛 bet 单双公式 博彩资讯网 w88优德 港京印刷图库 澳门足球盘 球盘 娱乐场 联众网站 沙龙365 7080棋牌游戏 真钱 澳门回归日期 大众娱乐网 卡宾官方旗舰店 mg电子 全亚洲首选288x nba即时比分 本港 中国足彩竞猜网 斗地主规则 七星彩论坛图规 网上赌博 单双规律 沈泳 华夏娱乐网 明升备用网址 梦网书城 盈禾国际 明升体育 博狗沃鑫 卡宾官方旗舰 信誉赌场 球探比分网 竞彩论坛空间 三亚娱乐 soutec 北京福利彩票 澳门彩票公司 北京pk10 凤凰 纸牌小游戏 皇冠现金代理 银河国际亚洲首选288x 三六八高手论坛 空中城市 赌球心得 188比分 让球规则 三星娱乐城 诺贝尔娱乐城 今天有nba直播吗 5060全讯网 新世纪 久乐娱乐场 世界杯盘口 易胜博 麻将单机版 华人娱乐总站 财神爷心水论坛 巴特 澳门百利宫 明升 红姐统一主图库 九龙老牌图库 打牌 娱乐王子 娱乐真人 亚豪平台 真人娱乐 飞禽走兽老虎机 188bet下载 博发娱乐城 北单 陈怀生 比分188 百万图库 老挝赌博 pc蛋蛋注册 天上人间娱乐 红姐统一图库 产业新闻网 葡京
网友评论(共1条评论) 正在载入评论......
理智评论文明上网,拒绝恶意谩骂 发表评论 / 共1条评论
登录会员中心