<li id="2aw4k"></li>
  • <div id="2aw4k"><tr id="2aw4k"></tr></div>
  • <div id="2aw4k"><tr id="2aw4k"></tr></div>
    <center id="2aw4k"><small id="2aw4k"></small></center><center id="2aw4k"><small id="2aw4k"></small></center>
    首页»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?#25105;?#21518;的结果。

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

     8 秒

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

    def self.success_callback(data)
    end  

      这是我归结为“仅仅是案例”(未来再验证!)的一类。原来,函数调用在Ruby?#20889;?#20215;十?#32844;?#36149;。仅仅删除它就节约了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秒的结果。这个库已经超过原来两倍速了!

      现在问题开?#21152;?#28857;更为复杂了。

     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?#21019;?#24314;新的方法时,它就会从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中方法调用很慢,我?#19981;?#23558;我的代码模块化的和重复使用,但也许是我开始内联代码的时候了。
    2. 测?#38405;?#30340;代码!删掉一个简单的未使用的方法花费我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会让我做更加优化?

     参考

      原文地址: QQ群:WEB开发者官方群(515171538),验证消息:10000
    微信群:加小编微信 849023636 邀请您加入,验证消息:10000

    提示:更多精彩内容关注微信公众号:全栈开发者?#34892;模╢sder-com)
    网友评论(共1条评论) 正在载入评论......
    理智评论文明上网,拒绝恶意谩骂 发表评论 / 共1条评论
    登录会员?#34892;?/span>
    大乐透彩票预测
    <li id="2aw4k"></li>
  • <div id="2aw4k"><tr id="2aw4k"></tr></div>
  • <div id="2aw4k"><tr id="2aw4k"></tr></div>
    <center id="2aw4k"><small id="2aw4k"></small></center><center id="2aw4k"><small id="2aw4k"></small></center>
    <li id="2aw4k"></li>
  • <div id="2aw4k"><tr id="2aw4k"></tr></div>
  • <div id="2aw4k"><tr id="2aw4k"></tr></div>
    <center id="2aw4k"><small id="2aw4k"></small></center><center id="2aw4k"><small id="2aw4k"></small></center>