2

According to my professor loops are faster and more deficient than using recursion yet I came up with this c++ code that calculates the Fibonacci series using both recursion and loops and the results prove that they are very similar. So I maxed the possible input to see if there was a difference in performance and for some reason recursion clocked in better than using a loop. Anyone know why? Thanks in advanced.

Here's the code:

#include "stdafx.h"
#include "iostream"
#include <time.h>
using namespace std;

double F[200000000];
//double F[5];

/*int Fib(int num)
{
    if (num == 0)
    {
        return 0;
    }

    if (num == 1)
    {
        return 1;
    }

    return Fib(num - 1) + Fib(num - 2);

}*/

double FiboNR(int n) // array of size n
{


    for (int i = 2; i <= n; i++)
    {
        F[i] = F[i - 1] + F[i - 2];
    }
    return (F[n]);
}

double FibMod(int i,int n) // array of size n
{
    if (i==n)
    {
        return F[i];
    }

    F[i] = F[i - 1] + F[i - 2];
    return (F[n]);
}

int _tmain(int argc, _TCHAR* argv[])
{
    /*cout << "----------------Recursion--------------"<<endl;
    for (int i = 0; i < 36; i=i+5)
    {
        clock_t tStart = clock();
        cout << Fib(i);
        printf("Time taken: %.2fs\n", (double)(clock() - tStart) / CLOCKS_PER_SEC);
        cout << " : Fib(" << i << ")" << endl;
    }*/

    cout << "----------------Linear--------------"<<endl;
    for (int i = 0; i < 200000000; i = i + 20000000)
    //for (int i = 0; i < 50; i = i + 5)
    {
        clock_t tStart = clock();
        F[0] = 0; F[1] = 1;
        cout << FiboNR(i);        
        printf("Time taken: %.2fs\n", (double)(clock() - tStart) / CLOCKS_PER_SEC);
        cout << " : Fib(" << i << ")" << endl;
    }

    cout << "----------------Recursion Modified--------------" << endl;
    for (int i = 0; i < 200000000; i = i + 20000000)
    //for (int i = 0; i < 50; i = i + 5)
    {
        clock_t tStart = clock();
        F[0] = 0; F[1] = 1;
        cout << FibMod(0,i);
        printf("Time taken: %.2fs\n", (double)(clock() - tStart) / CLOCKS_PER_SEC);
        cout << " : Fib(" << i << ")" << endl;
    }

    std::cin.ignore();
    return 0;
}
jdphenix
  • 15,022
  • 3
  • 41
  • 74
  • 6
    Please include the code that your question references in your question so that we can see it without accessing another site. – moreON Feb 05 '16 at 02:42
  • 3
    Just a guess, but you've probably got something that compiles to an optimized tail call recursion – jdphenix Feb 05 '16 at 02:44
  • 1
    "more deficient"? It's a rare description for me. Mind to elaborate? –  Feb 05 '16 at 02:44
  • Well, technically the question, might not have sense. If you don't have any side effects in your calculation, then compiler is free to transform one version to another. It's actually a stupid statement, which has a ton of loopholes, and should be protected by many assertions. – luk32 Feb 05 '16 at 02:46
  • Correct me if I'm wrong, but tail call optimization isn't possible for fibonacci - the recursive call has to be the very last. – nicebyte Feb 05 '16 at 02:48
  • 4
    You might notice that neither `FibNR` nor `FibMod` uses recursion. And in fact they do different things! `FibNR` computes all Fibonacci numbers up to the given point while `FibMod` only computes one of them (assuming all the rest have been calculated already) – user253751 Feb 05 '16 at 02:48
  • 1
    Fibonacci's recursion (without [memoization](https://en.wikipedia.org/wiki/Memoization)) is one of the worst examples of recursion, due to combinatorial explosion. – vsoftco Feb 05 '16 at 02:49
  • Most compilers will implement tail-call optimization when possible, which means they can automatically convert a recursive function into an equivalent iterative function, as long as the recursive call happens at the end of the function body. Given that, it's no surprise that the recursive function and the iterative function give the same performance, since they are probably compiling down to the same object code! (see http://stackoverflow.com/questions/310974/what-is-tail-call-optimization for more) – Jeremy Friesner Feb 05 '16 at 03:16
  • The answer is "no", because a properly optimised loop is exactly as fast as a properly optimised tail recursion. Loops are not faster than recursion. The answer is also "yes", because in light of the above a properly optimised recursion is the same speed, or slower than, a properly optimised iterative solution. – paddy Feb 05 '16 at 03:58
  • @NickyC presumably "more deficient" was meant to be "more efficient". –  Feb 05 '16 at 20:06

3 Answers3

7

You you go by the conventional programming approach loops are faster. But there is category of languages called functional programming languages which does not contain loops. I am a big fan of functional programming and I am an avid Haskell user. Haskell is a type of functional programming language. In this instead of loops you use recursions. To implement fast recursion there is something known as tail recursion. Basically to prevent having a lot of extra info to the system stack, you write the function such a way that all the computations are stored as function parameters so that nothing needs to be stored on the stack other that the function call pointer. So once the final recursive call has been called, instead of unwinding the stack the program just needs to go to the first function call stack entry. Functional programming language compilers have an inbuilt design to deal with this. Now even non functional programming languages are implementing tail recursion.

For example consider finding the recursive solution for finding the factorial of a positive number. The basic implementation in C would be

int fact(int n)
{
  if(n == 1 || n== 0)
       return 1
   return n*fact(n-1);

}

In the above approach, each time the stack is called n is stored in the stack so that it can be multiplied with the result of fact(n-1). This basically happens during stack unwinding. Now check out the following implementation.

int fact(int n,int result)
{
   if(n == 1 || n== 0)
       return result

       return fact(n-1,n*result);

}

In this approach we are passing the computation result in the variable result. So in the end we directly get the answer in the variable result. The only thing you have to do is that in the initial call pass a value of 1 for the result in this case. The stack can be unwound directly to its first entry. Of course I am not sure that C or C++ allows tail recursion detection, but functional programming languages do.

Ratan Senapathy
  • 2,148
  • 2
  • 10
  • 14
  • 1
    C and C++ both allow detection, under the as-if rule, but don't require compilers to convert tail recursion to iteration – Ben Voigt Feb 05 '16 at 03:21
  • 1
    Thanks for letting me know – Ratan Senapathy Feb 05 '16 at 05:10
  • 1
    Very interesting that was exactly the approach that I was looking for. So what your proposing is instead of taking a top down approach we would implement a bottom up allowing us to reuse previous calculation without having to rely on the unwinding of the stack. Makes sense thanks for your help. – Andres Toledo Feb 27 '16 at 18:23
1

Your "recursion modified" version doesn't have recursion at all.

In fact, the only thing enabling a non-recursive version that fills in exactly one new entry of the array is the for-loop in your main function -- so it is actually a solution using iteration also (props to immibis and BlastFurnace for noticing that).

But your version doesn't even do that correctly. Rather since it is always called with i == 0, it illegally reads F[-1] and F[-2]. You are lucky (?)1 the program didn't crash.

The reason you are getting correct results is that the entire F array is prefilled by the correct version.

Your attempt to calculate Fib(2000....) isn't successful anyway, since you overflow a double. Did you even try running that code?

Here's a version that works correctly (to the precision of double, anyway) and doesn't use a global array (it really is iteration vs recursion and not iteration vs memoization).

#include <cstdio>
#include <ctime>
#include <utility>


double FiboIterative(int n)
{
    double a = 0.0, b = 1.0;

    if (n <= 0) return a;

    for (int i = 2; i <= n; i++)
    {
        b += a;
        a = b - a;
    }
    return b;
}

std::pair<double,double> FiboRecursive(int n)
{
    if (n <= 0) return {};

    if (n == 1) return {0, 1};

    auto rec = FiboRecursive(n-1);

    return {rec.second, rec.first + rec.second};
}

int main(void)
{
    const int repetitions = 1000000;
    const int n = 100;
    volatile double result;

    std::puts("----------------Iterative--------------");
    std::clock_t tStart = std::clock();
    for( int i = 0; i < repetitions; ++i )
        result = FiboIterative(n);
    std::printf("[%d] = %f\n", n, result);
    std::printf("Time taken: %.2f us\n", (std::clock() - tStart) / 1.0 / CLOCKS_PER_SEC);

    std::puts("----------------Recursive--------------");
    tStart = std::clock();
    for( int i = 0; i < repetitions; ++i )
        result = FiboRecursive(n).second;
    std::printf("[%d] = %f\n", n, result);
    std::printf("Time taken: %.2f us\n", (std::clock() - tStart) / 1.0 / CLOCKS_PER_SEC);
    return 0;
}

--

1Arguably anything that hides a bug is actually unlucky.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • your absolutely right, must have not been thinking right when I set up this test. Thanks for the feedback I will get to re-writing the recursive function. – Andres Toledo Feb 27 '16 at 18:17
-1

I don't think this is not a good question. But maybe the answer why is somehow interesting.

At first let me say that generally the statement is probably true. But ...

Questions about performance of c++ programs are very localized. It's never possible to give a good general answer. Every example should be profiled an analyzed separately. It involves lots of technicalities. c++ compilers are allowed to modify program practically as they wish as long as they don't produce visible side effects (whatever precisely that means). So as long as your computation gives the same result is fine. This technically allows to transform one version of your program into an equivalent even from recursive version into the loop based and vice versa. So it depends on compiler optimizations and compiler effort.

Also, to compare one version to another you would need to prove that the versions you compare are actually equivalent.

It might also happen that somehow a recursive implementation of algorithm is faster than a loop based one if it's easier to optimize for the compiler. Usually iterative versions are more complex, and generally the simpler the code is, the easier it is for the compiler to optimize because it can make assumptions about invariants, etc.

luk32
  • 15,812
  • 38
  • 62