簡體   English   中英

為什么遞歸比JavaScript上的求和函數的for循環更快?

[英]Why is recursion faster than a flat for loop for a summation function on JavaScript?

我正在使用一種翻譯成JavaScript的語言。 為了避免一些堆棧溢出,我通過將某些函數轉換為for循環來應用尾調用優化。 令人驚訝的是,轉換並不比遞歸版本快。

http://jsperf.com/sldjf-lajf-lkajf-lkfadsj-f/5

遞歸版:

(function recur(a0,s0){
    return a0==0 ? s0 : recur(a0-1, a0+s0)
})(10000,0)

尾部調用優化后:

ret3 = void 0;
a1   = 10000;
s2   = 0;
(function(){
    while (!ret3) {
        a1 == 0 
            ? ret3     = s2
            : (a1_tmp$ = a1 - 1 ,
               s2_tmp$ = a1 + s2,
               a1      = a1_tmp$,
               s2      = s2_tmp$);
     }
})();
ret3;

使用Google Closure Compiler進行一些清理之后:

ret3 = 0;
a1   = 1E4;
for(s2 = 0; ret3 == 0;)
    0 == a1 
        ? ret3     = s2 
        : (a1_tmp$ = a1 - 1 ,
           s2_tmp$ = a1 + s2,
           a1      = a1_tmp$,
           s2      = s2_tmp$);
c=ret3;

遞歸版本比“優化”版本更快! 如果遞歸版本必須處理數以千計的上下文更改,那怎么可能呢?

優化比尾部調用優化更多。

例如,我注意到你正在使用兩個臨時變量,只需要:

s2 += a1;
a1--;

僅這一點實際上將操作次數減少了三分之一,從而使性能提高了50%

從長遠來看,在嘗試優化操作本身之前優化正在執行的操作非常重要。

編輯:這是一個更新的jsperf

正如Kolink所說,你的代碼所做的只是在總數中加n ,將n減1,然后循環直到n不到0

所以就這樣做:

n = 10000, o = 0; while(n) o += n--;

它比遞歸版本更快更快 ,並且當然輸出相同的結果

遞歸版本中的上下文變化並不像您期望的那么多,因為命名函數recur包含在recur本身的范圍內/它們共享相同的范圍。 其原因與JavaScript引擎評估范圍的方式有關,並且有很多網站可以解釋這個主題,所以我不會在這里做。 再看看你會注意到recur也是一個所謂的“純”函數,這基本上意味着只要內部執行運行它就永遠不會離開它自己的范圍(簡單地說:直到它返回一個值)。 這兩個事實使它基本上很快。 我只想在這里提一下,第一個例子是所有三個中唯一的尾部調用優化 - tc優化只能在遞歸函數中完成,這是唯一的遞歸函數。

然而,第二個看第二個例子(沒有雙關語)揭示了“優化器”使你的事情變得更糟,因為它通過將操作分成以前的操作將范圍引入到前一個純函數中

  • 變量而不是參數
  • while循環
  • IIFE(即時調用的函數表達式),用於分隔引入的內部和外部變量

這導致性能較差,因為現在引擎必須處理10000個上下文更改。

說實話我不知道為什么第三個例子的性能比遞歸的差,所以它可能與:

  • 您使用的瀏覽器(曾嘗試過另一個並比較結果?)
  • 變量的數量
  • 由for循環創建的堆棧幀(從未聽說過),這與第一個示例有關:JS引擎解釋純遞歸函數,直到找到return語句。 如果語句后面的最后一件事是函數調用,那么計算任何表達式(如果有的話)和變量作為參數傳遞,調用函數並丟棄框架
  • 東西,只有瀏覽器供應商才能真正告訴你:)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM