简体   繁体   English

为什么递归比JavaScript上的求和函数的for循环更快?

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

I'm working in a language that translates to JavaScript. 我正在使用一种翻译成JavaScript的语言。 In order to avoid some stack overflows, I'm applying tail call optimization by converting certain functions to for loops. 为了避免一些堆栈溢出,我通过将某些函数转换为for循环来应用尾调用优化。 What is surprising is that the conversion is not faster than the recursive version. 令人惊讶的是,转换并不比递归版本快。

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

Recursive version: 递归版:

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

After tail call optimization: 尾部调用优化后:

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;

After some cleanup using Google Closure Compiler: 使用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;

The recursive version is faster than the "optimized" ones! 递归版本比“优化”版本更快! How can this be possible, if the recursive version has to handle thousands of context changes? 如果递归版本必须处理数以千计的上下文更改,那怎么可能呢?

There's more to optimising than tail-call optimisation. 优化比尾部调用优化更多。

For instance, I notice you're using two temporary variables, when all you need is: 例如,我注意到你正在使用两个临时变量,只需要:

s2 += a1;
a1--;

This alone practically reduces the number of operations by a third, resulting in a performance increase of 50% 仅这一点实际上将操作次数减少了三分之一,从而使性能提高了50%

In the long run, it's important to optimise what operations are being performed before trying to optimise the operations themselves. 从长远来看,在尝试优化操作本身之前优化正在执行的操作非常重要。

EDIT: Here's an updated jsperf 编辑:这是一个更新的jsperf

as Kolink say what your piece of code do is simply adding n to the total, reduce n by 1, and loop until n not reach 0 正如Kolink所说,你的代码所做的只是在总数中加n ,将n减1,然后循环直到n不到0

so just do that : 所以就这样做:

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

it's more faster and lisible than the recursive version, and off course output the same result 它比递归版本更快更快 ,并且当然输出相同的结果

There are not so much context changes inside the recursive version as you expect, since the named function recur is contained in the scope of recur itself/they share the same scope. 递归版本中的上下文变化并不像您期望的那么多,因为命名函数recur包含在recur本身的范围内/它们共享相同的范围。 The reason for that has to do with the way the JavaScript engines evaluate scope, and there are plenty of websites out there which explain this topic, so I will not do it here. 其原因与JavaScript引擎评估范围的方式有关,并且有很多网站可以解释这个主题,所以我不会在这里做。 At a second look you will notice that recur is also a so called "pure" function, which basically means it never has to leave it's own scope as long as the internal execution runs (simply put: until it returns a value). 再看看你会注意到recur也是一个所谓的“纯”函数,这基本上意味着只要内部执行运行它就永远不会离开它自己的范围(简单地说:直到它返回一个值)。 These two facts make it basically fast. 这两个事实使它基本上很快。 I just want to mention here, the first example is the only tail call optimized one of all three – a tc optimization can only be done in recursive functions and this is the only recursive one. 我只想在这里提一下,第一个例子是所有三个中唯一的尾部调用优化 - tc优化只能在递归函数中完成,这是唯一的递归函数。

However, a second look at the second example (no pun intended) reveals, that the "optimizer" made things worse for you, since it introduced scopes into the former pure function by splitting the operation into 然而,第二个看第二个例子(没有双关语)揭示了“优化器”使你的事情变得更糟,因为它通过将操作分成以前的操作将范围引入到前一个纯函数中

  • variables instead of arguments 变量而不是参数
  • a while loop while循环
  • a IIFE (immediatly invoked function expression) that separates the introduced inner and outer variables IIFE(即时调用的函数表达式),用于分隔引入的内部和外部变量

Which leads to poorer performance since now the engine has to handle 10000 context changes. 这导致性能较差,因为现在引擎必须处理10000个上下文更改。

To tell you the truth I do not know why the third example is poorer in performance than the recursive one, so maybe it has to do with: 说实话我不知道为什么第三个例子的性能比递归的差,所以它可能与:

  • the browser you use (ever tried another one and compared the results?) 您使用的浏览器(曾尝试过另一个并比较结果?)
  • the number of variables 变量的数量
  • stack frames created by for-loops (never heard of though), which would have to do with the first example: the JS engines interpret a pure recursive function until it finds a return statement. 由for循环创建的堆栈帧(从未听说过),这与第一个示例有关:JS引擎解释纯递归函数,直到找到return语句。 If the last thing following the statement is a function call, then evaluate any expressions (if any) and variables to pass as arguments, call the function and throw away the frame 如果语句后面的最后一件事是函数调用,那么计算任何表达式(如果有的话)和变量作为参数传递,调用函数并丢弃框架
  • something, only the browser-vendors can truly tell you :) 东西,只有浏览器供应商才能真正告诉你:)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 为什么 then 回调中的这个循环比异步 function 中的循环更快? - Why is this loop in a then callback faster than in an async function? 为什么递归似乎比JavaScript中的for循环要慢得多? - Why recursion seems to be much slower than for-loop in JavaScript? JavaScript 循环性能 - 为什么将迭代器递减到 0 比递增更快 - JavaScript loop performance - Why is to decrement the iterator toward 0 faster than incrementing 在简单的循环测试中,Javascript比Classical C快100,为什么? - Javascript is 100 faster than Classical C in simple for loop test, why? 在JavaScript中,为什么“反向”循环比“for”快一个数量级? - In JavaScript, why is a “reverse while” loop an order of magnitude faster than “for”? 有没有比 for 循环更快的方法来对 javascript 中的图像进行阈值处理? - Is there a faster way than a for loop for thresholding an image in javascript? 为什么 lodash _.each 比原生 for 循环快? - Why is lodash _.each faster than the native for loop? 为什么for循环的100000次迭代比50000快? - Why are 100000 iterations of a for loop faster than 50000? forEach函数比等效的for循环快得多 - forEach function much faster than equivalent for loop JavaScript:为什么原生Array.prototype.map比Chrome控制台中的循环更快? - JavaScript: Why is native Array.prototype.map faster than for loop in Chrome console?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM