Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is recursion in javascript so slow?

I just ran this benchmark on jsperf: https://jsperf.com/mapping1

I was trying to see if a map that used recursion could beat the Array.prototype map function. Mine lost. Horribly. Can someone explain why?

map = function(f, xs) {
    if (xs.length === 0) {return []}
    return [f(head(xs))].concat(map(f, tail(xs)))
}

// head() and tail() do exactly what you would expect. I wish there was a way to programmatically fork lists in js...
like image 568
dopatraman Avatar asked Aug 10 '15 20:08

dopatraman


People also ask

Is recursion slow in JavaScript?

No. Recursion code is simpler and cleaner. The stack manipulation code is in the subroutine entry/exit code instead of the user code. As a compiler writer, writing compilers using recursion makes the architecture simpler than any other techniques.

Why is recursion so slow?

The simplicity of recursion comes at the cost of time and space efficiency. It is much slower than iteration due to the overhead of function calls and control shift from one function to another. It requires extra memory on the stack for each recursive call. This memory gets deallocated when function execution is over.

Is recursion good in JavaScript?

However, recursion may not always be such a good idea when it results in an algorithm that is memory and computationally intensive, especially in languages such as JavaScript, where tail-call optimization is not well supported.

Is recursion faster JavaScript?

Recursion is still faster than iteration, but not by very much, as in the first case. Recursion: “Maximum call stack size exceeded.” Wow, what just happened? We have an error in the recursion case because it adds every function call to the call stack.


1 Answers

Here is implementation of fasterMap recursively, but without concat, it is 20x faster than map and only 1.5x slower than native Array.map:

var Cx = function(){
    this.map = function (f, xs) {
        if (xs.length === 0) {return []}
        return [f(head(xs))].concat(arguments.callee(f, tail(xs)))
    }

    this.fasterMap = function(f, xs, i) {
        i = i || 0;
        if (xs.length === 0 || i > xs.length - 1) {return []}
        xs[i] = f(xs[i])
        arguments.callee(f, xs, i + 1)
        return xs
    }

    this.arrMap = function (f, xs) {
        return xs.map(f)
    }
}

function head(arr){return arr[0]}
function tail(arr){return arr.slice(1)}

function add1(x){return x + 1}

function rep(n,f){
    for(var i = 0; i < n; i++)
        f(i)
}

var cx = new Cx()

;[9,99,999].forEach(function(n){
    var test = []
    rep(n,function(i){test.push(i + 1)})

    ;['map','fasterMap','arrMap'].forEach(function(mapType){
        var mapFn = function(){return cx[mapType](add1,test)}
        if(n < 10)
            console.log('    ' + mapType,mapFn())
        else{
            console.time('    ' + mapType + ' ' + n)
            rep(1000,mapFn)
            console.timeEnd('    ' + mapType + ' ' + n)
        }
    })
})

Here are test results on Cloud9 IDE:

map [ 2, 3, 4, 5, 6, 7, 8, 9, 10 ]                                                                                                                                                                                                                    
fasterMap [ 2, 3, 4, 5, 6, 7, 8, 9, 10 ]                                                                                                                                                                                                              
arrMap [ 3, 4, 5, 6, 7, 8, 9, 10, 11 ]                                                                                                                                                                                                                

map 99: 45ms                                                                                                                                                                                                                                          
fasterMap 99: 8ms                                                                                                                                                                                                                                     
arrMap 99: 7ms                                                                                                                                                                                                                                        

map 999: 2227ms                                                                                                                                                                                                                                       
fasterMap 999: 102ms                                                                                                                                                                                                                                  
arrMap 999: 85ms 

So the answer is concat makes your map function slow.

like image 112
alpav Avatar answered Oct 16 '22 03:10

alpav