<dl id="3wz6h"></dl><li id="3wz6h"></li>

      1. <dl id="3wz6h"></dl>

      2. <dl id="3wz6h"><ins id="3wz6h"></ins></dl>

            <dl id="3wz6h"></dl>

            <dl id="3wz6h"><ins id="3wz6h"></ins></dl>
            1. 
              
              <output id="3wz6h"><ins id="3wz6h"><nobr id="3wz6h"></nobr></ins></output>

              <li id="3wz6h"><ins id="3wz6h"></ins></li>
              
              

            2. <output id="3wz6h"><ins id="3wz6h"><nobr id="3wz6h"></nobr></ins></output>
              首頁»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群:WEB開發者官方群(515171538),驗證消息:10000
              微信群:加小編微信 849023636 邀請您加入,驗證消息:10000
              提示:更多精彩內容關注微信公眾號:全棧開發者中心(fsder-com)
              網友評論(共1條評論) 正在載入評論......
              理智評論文明上網,拒絕惡意謾罵 發表評論 / 共1條評論
              登錄會員中心
              云南十一选往期