繁体   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