简体   繁体   中英

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. In order to avoid some stack overflows, I'm applying tail call optimization by converting certain functions to for loops. What is surprising is that the conversion is not faster than the recursive version.

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:

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%

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

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

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. 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. 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). 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.

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
  • a IIFE (immediatly invoked function expression) that separates the introduced inner and outer variables

Which leads to poorer performance since now the engine has to handle 10000 context changes.

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. 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 :)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM