You’re probably all aware of the Fibonacci number sequence. Starting with 0 and 1, the next number is determined by the sum of the previous two numbers, so the sequence begins:
0, 1, 1, 2, 3, 5, 8, 13, 21, …
This is a mathematical concept, and it is defined by a “recurrence relation” (that’s a fancy way of saying that each number (or ‘term’) is generated from a previously calculated value), of:
Fn = Fn-1 + Fn-2
with “seed” or starting values of:
F0 = 0 and F1 = 1.
So, as you can imagine, this seems to lend itself nicely to programmatic recursion. Here’s a little program that will give you the 40th number in the Fibonacci sequence, using recursion:
#include <iostream> using namespace std; int fib(int i) { if (i == 0) { return 0; } else if ((i == 1) || (i == 2)) { return 1; } else { return (fib(i-1) + fib(i-2)); //here's our recursive call } } int main() { int n = 40; cout << fib(n); return 0; }
All very nice.
Next, I wrote the same program again, this time not using recursion:
#include <iostream> using namespace std; int fib(int x) { int number1 = 0; int number2 = 1; int next = 1; for (int i = 0 ; i < x-1 ; ++i) { next = number1 + number2; number1 = number2; number2 = next; } return next; } int main() { int n = 40; cout << fib(n); return 0; }
Then I compiled both of them using a standard compile:
g++ -Wall fib_recursive.cpp -o fibr g++ -Wall fib_normal.cpp -o fibn
And I timed the execution of them using the bash time command:
What’s the thing that jumps straight out at you here?
That’s right. How much slower it is to use the function recursively. We’ve gone from a barely detectable execution time, to a delay of almost one and a half seconds – that’s nearly 500 times slower than the non-recursive version.
What about optimisation?
Well, that’s what I thought. So next, I compiled them with the -O3 flag:
g++ -Wall -O3 fib_recursive.cpp -o fibr g++ -Wall -O3 fib_normal.cpp -o fibn
And then I repeated the timing:
You can see that, pretty impressively, the recursive version is now running around 60% faster than it was. However, it is still around 180 times slower than the non-recursive version.
So, what can we learn from this?
You might be wondering if it is ever right to use recursion. Recursive functions in programs (written in C and C++) are almost always expensive (i.e. they take more time than non-recursive functions). If speed is your number one priority, you would do well to avoid recursion where possible.
However (and there’s always a ‘but’), recursion can often present itself more elegantly, and be potentially easier to understand and maintain, than the equivalent non-recursive code. This does however depend on your comfort level with dealing with recursion – so it’s not going to be easier to maintain and understand if recursion gives you a headache every time you think about it.
So, to takeaway here:
- Recursion is a mathematical concept that can be implemented programmatically
- Recursion in C/C++ is almost always slower than the equivalent non-recursive code
- Recursion can provide an elegant solution that is easier for other programmers to understand
- Whether this is true in the long run depends on the skill of the individual programmers who will be maintaining the program
- If speed is your number one priority, recursion is probably not the best solution
- Understanding recursion is nonetheless an important programming skill
There’s another, a bit more awkward, recursion style that is almost as performant as your fib_normal.cpp. It’s kinda like an inside-out for loop (pardon the highly technical computer science terminology). 😎
#include
using namespace std;
int fib_recurse(int f0, int f1, int s1, int s2)
{
if (s1 == s2)
{
return f1;
}
else
{
return fib_recurse(f1, f0 + f1, s1 + 1, s2); // recursive call here
}
}
int fib(int i)
{
if (i == 0)
{
return 0;
}
else if ((i == 1) || (i == 2))
{
return 1;
}
else
{
return fib_recurse(0, 1, 1, i);
}
}
int main()
{
int n = 40;
cout << fib(n);
return 0;
}