1

Is there any way to speed up the execution of this recursive function and keeping it recursive?

var printDecreased = function(z) {
    console.log(z);

    if (z > 0) {
        printDecreased(z-1);
    }
};

printDecreased(50);

Since this has a single recursive call I don't think there are many ways to speed up it

giò
  • 3,402
  • 7
  • 27
  • 54

4 Answers4

1

You can increase the speed a slight bit by avoiding the if statement like so:

var printDecreased = function(z) {
    console.log(z);

    (z > 0 && printDecreased(z-1));
};

printDecreased(50);

But the increase is only very slight (about 5-10%)

How does that work?

The keyword here is branch prediction. I will not go into detail about branch prediction, since that's not what the question was about. Bottom line is, branches cost quite a lot of CPU time, which means, each if is quite expensive and if you want to improve the speed of a heavily used line then try your best to leave out all branches.

The easiest way to do that is by replacing if/else constructs that only contain one statement on each side by ternary operators and if constructs that contain only one statement in total by the && or || operators. These are generally speaking much faster than ifs or if/elses. This can be applied to almost any programming language that supports these constructs.

Examples

if (x==y) {
    callA();
} else {
    callB();
}

can be replaced with

(x==y ? callA() : callB());

and

if (x==y) {
    callA();
}

can be replaced by

(x==y && callA());

or

(x!=y || callA());

These options give the compiler better clues on how to optimize, so it can optimize the branch prediction.

Other possible optimizations The example can hardly be optimized any better than it is so far, with the given limitations. Since this is such a trivial function the function call overhead makes up quite a substantial part of the execution time. If you can replace the recursion with a loop (which is easily done in the given example) you can, generally speaking, speed up the process quite a lot as well. Removing recursions speeds up functions in most programming languages, since calling functions usually costs a lot of CPU time.

Dakkaron
  • 5,930
  • 2
  • 36
  • 51
  • Are you sure this would increase the speed? I think checking for `z > 0` should reduce to a `jnz` instruction either way. Also, now adding the `&&` operation should add more processing time, not take away from it. – A. Duff Jun 01 '15 at 13:56
  • 1
    I tested it in Chrome and Firefox and in both cases it sped up the function, though not much. And in almost all languages the && operator is a bit faster than an if, since it reduces branching cost by quite a bit. – Dakkaron Jun 01 '15 at 14:04
  • Thanks. After you posted that, I was interested so I found [this](http://stackoverflow.com/questions/11227809/why-is-processing-a-sorted-array-faster-than-an-unsorted-array/11227902#11227902). I didn't know about branch prediction. I still don't understand why this is treated any differently than `&&` by the compiler/CPU though. – A. Duff Jun 01 '15 at 14:36
  • That's actually quite simple, I'll add the explanation to the answer – Dakkaron Jun 01 '15 at 14:39
  • Thanks. I actually meant that I was confused about why logical/ternary operators would be compiled/interpreted differently than conditional branches. I asked a question about this [here](http://stackoverflow.com/questions/30621933/why-are-ternary-and-logical-operators-more-efficient-than-if-branches), but the answers are saying that this is not the case. Since you've tested it and gotten different runtimes, feel free to add your own answer if you understand why this is the case. – A. Duff Jun 03 '15 at 19:44
1

One possible way to speed up the function would be to add a little bit of redundancy to it. For example, using Duff's device we can attempt to boost the performance of the function:

function printDecreasedOptimized(z) {
    console.log(z);

    if (z > 0) {
        switch (z % 8) {
        case 0: console.log(--z);
        case 7: console.log(--z);
        case 6: console.log(--z);
        case 5: console.log(--z);
        case 4: console.log(--z);
        case 3: console.log(--z);
        case 2: console.log(--z);
        case 1: console.log(--z);
        }

        (z > 0 && printDecreasedDuff(z));
    }
}

function printDecreasedDuff(z) {
    console.log(--z);
    console.log(--z);
    console.log(--z);
    console.log(--z);
    console.log(--z);
    console.log(--z);
    console.log(--z);
    console.log(--z);

    (z > 0 && printDecreasedDuff(z));
}

The idea is to perform eight iterations at a time, thereby saving the cost of seven function call overheads. Surprisingly, on benchmarking this code I found that this optimization doesn't make much of a difference to performance.

I surmise that modern JavaScript engines like V8 and SpiderMonkey are smart enough to optimize such recursive functions. Therefore, you don't need to worry about performance of such functions. Indeed, premature optimization is the root of all evil.

BTW, with proper tails calls around the corner your recursive functions will only become faster.

Aadit M Shah
  • 72,912
  • 30
  • 168
  • 299
  • I am not quite sure if this answer really fits to the question, since it is based on getting around the recursion, even if only partially. – Dakkaron Jun 02 '15 at 01:05
-1

you may take a look at memoize. Underscore claims that it is Useful for speeding up slow-running computations

codeofnode
  • 18,169
  • 29
  • 85
  • 142
  • Memoization won't work here, memoization proves to be helpful when you need old already calculated data by the function for the calculation of something new. e.g calculation of a new term in Fibonacci sequence requires addition of two older terms. – Nash Vail Jun 01 '15 at 16:00
-1

Simply shortened the function name. Created a performance test at jsperft.

var p = function(z) {
    console.log(z);

    (z > 0 && p(z-1));
};
Misa Lazovic
  • 2,805
  • 10
  • 32
  • 38
  • The JS engine optimizes ternary operator and plain if statements to have the same effect/performance. In fact, even in [tag:c] language the is no difference between them. – Christos Lytras Aug 12 '17 at 17:54