8

As a joke a few months ago, a coworker of mine sought out to "speed up the heat death of the universe" by calculating fibonacci numbers using this exponential algorithm:

int Fib(int n)
{
    if (n <= 1)
        return 1;
    else
        return Fib(n - 1) + Fib(n - 2);
}

How does this not cause a stackoverflow in C#? We managed to get to Fib(52) before giving up (and Fib(51) took many hours). I would think this would hammer the stack hard enough to cause a stack overflow, since the CLR by default only allocates 1M to the stack. Also, I'm pretty sure this isn't eligible for tail recursion either.

Earlz
  • 62,085
  • 98
  • 303
  • 499
  • So, how many bytes do you think get pushed to the stack on each recursion? – Roger Rowland Mar 07 '13 at 20:06
  • What version of the C# compiler and framework are you using? Have you looked at the generated MSIL or the [de]compiled assembler that it gets JIT'ed to? Entirely possible that the C# compiler recognizes the parse tree as fibonacci sequence and does a special case optimization. – Nicholas Carey Mar 07 '13 at 20:07
  • 2^52 is my super naive off the top of my head calculation, heh, since the complexity is O(2^n) – Earlz Mar 07 '13 at 20:07
  • @NicholasCarey .Net 4.5, latest everything (VS2012, etc). Didn't look at the generated IL, but I don't imagine it'd have any optimizations there – Earlz Mar 07 '13 at 20:08
  • @Earlz: see my amended comment above. – Nicholas Carey Mar 07 '13 at 20:10
  • You might want to read this too - http://stackoverflow.com/questions/10321088/stack-overflow-with-fibonaccis-recursive-call – Roger Rowland Mar 07 '13 at 20:11
  • 2
    Time complexity and space complexity are not the same thing. The program is O(n) in stack space, and O(Fib(n)) in time. (Interesting thing about naive fib is that runtime is proportional to the magnitude of the output.) – Eric Lippert Mar 07 '13 at 21:27
  • 2
    Also, you did not calculate the 52nd fib number. Fib numbers above the 47th are larger than int.MaxValue. You overflowed silently. – Eric Lippert Mar 07 '13 at 21:32
  • @EricLippert I just ripped this off of another SO question. I believe in what we actually wrote we used longs – Earlz Mar 08 '13 at 02:21

2 Answers2

21

The recursive calls are not computed at the same time, but sequentially, meaning that Fib(n - 2) will only compute after Fib(n - 1) (or the other way around). This means that even though you create 2^n recursive calls, only n will be active at the same time. Therefore Fib(52) will only need space for 52 stackframes of Fib, which doesn't take any noticable stack space.

Grizzly
  • 19,595
  • 4
  • 60
  • 78
  • +1. Stack depth is too small for any problems. To get something evil out of such code one should to start computation in parallel (but still stack depth will not be the issue). – Alexei Levenkov Mar 07 '13 at 20:10
  • @AlexeiLevenkov one proposed way to "speed it up" was to spawn a new thread to calculate each fib(x) in parallel! lol – Earlz Mar 07 '13 at 20:11
3

The naive Fibonacci implementation indeed generates a large number of function calls (equal to the result, in fact), but it doesn't recurse very deeply. The maximum depth of recursion is n.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • (In the naive approach) Why is the number of function calls equal to the Fibonacci number that is being computed? I mean, you always end up calling `F(0)` and `F(1)` in the end, so a Fibonacci number is fundamentally the number of calls to `F(1)` you have to make to compute it via the naive approach, but what about all the other calls? For instance, `3 = F(4) = F(3) + F(2) = F(2) + F(1) + F(1) + F(0) = F(1) + F(0) + F(1) + F(1) + F(0)`, and 3 indeed the number of calls to `F(1)`, not the number of calls to `F` in general. – Enlico Oct 02 '22 at 15:48
  • @Enlico: oops, I think it's a different Fibonacci number farther into the sequence and not the result. – Ben Voigt Oct 03 '22 at 15:24