Python Forum

Full Version: Why recursive function consumes more of processing time than loops?
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
Hi All,

Why recursive function consumes more processing time than loops?
And, Is it cumbersome for the processor?

def fibonacci(n):
    if n == 0:
        return 0
    elif n == 1:
        return 1
    else:
        return fibonacci(n-1) + fibonacci(n-2)

print fibonacci(40)
^^^^ this example takes about 1 min, but with loop only few ms
I assume your loop is not written identically.

Recursion means a function call. A function call will take more time and memory than just executing a loop. But the difference isn't huge.

But your program is written in a way where you're redoing your work over and over and over. I'm assuming your loop was not written that way. The slowness is due to the algorithm, not inherently the recursion.

If you add a memoization cache to it, it will complete quickly.
from functools import lru_cache

@lru_cache
def fibonacci(n):
    if n == 0:
        return 0
    elif n == 1:
        return 1
    else:
        return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(40))
(May-11-2021, 11:50 PM)bowlofred Wrote: [ -> ]If you add a memoization cache to it, it will complete quickly.

Thank You alot Thumbs Up

(May-11-2021, 11:50 PM)bowlofred Wrote: [ -> ]
from functools import lru_cache

i can't import that coz i'm using v2.7
Python 2 is very old and there's lots of things that no longer support it. You should figure out what is preventing you from using python 3 and try to address it.
I ran this code
def fib(n):
    fib.count += 1
    if n < 2:
        return 1
    else:
        return fib(n-2) + fib(n-1)

fib.count = 0
print(fib(40))
print(fib.count)
The fib function was called 331,160,281 times

Then I ran this code that uses a dictionary to save and reuse partial results:
def fib(n):
    fib.count += 1
    a = partial_results.get(n, None)
    if a is None:
        a = fib(n-2) + fib(n-1)
        partial_results[n] = a
    return a

partial_results = {0:0, 1:1}
fib.count = 0
print(fib(40))
print(fib.count)
This is similar to what Python does for you using the cache, but expanded out so you can see the mechanism. The result was the same, but the fib function is only called 79 times. If I were to compute multiple Fibonacci numbers many of them could be calculated by a single call to fib.

So raw recursion takes longer because it is doing a ridiculous number of function calls calculating the same results over and over and over.
(May-12-2021, 03:06 AM)deanhystad Wrote: [ -> ]The fib function was called 331,160,281 times

Shocked
I'm shocked.
trying to understand from where this huge number come!
Starting with a really simple case:
fib[4] = fib[3] + fib[2]
fib[4] = (fib[3] = fib[2] + fib[1]) + (fib[2] = fib[1] + fib[0])
fib[4] = (fib[3] = (fib[2] = fib[1] + fib[0]) + fib[1]) + (fib[2] = fib[1] + fib[0])
That's 9 calls to fib().

This code records the number of times fib[n] is called for each value of n:
calls = {}

def fib(n):
    calls[n] = calls.get(n, 0) + 1
    if n < 2:
        return 1
    else:
        return fib(n-1) + fib(n-2)
fib(5)
for n, count in calls.items():
    print(f'{n:3d} : {count:5d}')
print('Total', sum(calls.values()))
Output:
5 : 1 4 : 1 3 : 2 2 : 3 1 : 5 0 : 3 Total 15
Just doubling starting n from 5 to 10 does this:
Output:
10 : 1 9 : 1 8 : 2 7 : 3 6 : 5 5 : 8 4 : 13 3 : 21 2 : 34 1 : 55 0 : 34 Total 177
And this is the horrible thing that happens when n = 40
Output:
40 : 1 39 : 1 38 : 2 37 : 3 36 : 5 35 : 8 34 : 13 33 : 21 32 : 34 31 : 55 30 : 89 29 : 144 28 : 233 27 : 377 26 : 610 25 : 987 24 : 1597 23 : 2584 22 : 4181 21 : 6765 20 : 10946 19 : 17711 18 : 28657 17 : 46368 16 : 75025 15 : 121393 14 : 196418 13 : 317811 12 : 514229 11 : 832040 10 : 1346269 9 : 2178309 8 : 3524578 7 : 5702887 6 : 9227465 5 : 14930352 4 : 24157817 3 : 39088169 2 : 63245986 1 : 102334155 0 : 63245986 Total 331160281
(May-17-2021, 06:29 PM)deanhystad Wrote: [ -> ]Starting with a really simple case:

that makes sense now, i benefited a lot from you
thank you
This is why using recursion shouldn't be taken for granted. The use of resources can easily become exponential; and the more resources are used per function call, the worse it'll be.

While recursion can provide a simpler and more readable solution to a problem, you'll be able to achieve similar results if you use the right tools. For instance, this code:

def fib(n):
    fibs = []
    for i in range(n + 1):
        if i < 2:
            fibs.append(i)
        else:
            fibs.append(fibs[i - 2] + fibs[i - 1])
    return fibs[-1] if n >= 0 else 0
Provides the same simple solution by making use of a list to save and use the results as we iterate from 0 to n (included). The memory footprint in this case is minimal (only one function call and a list), as we don't take additional steps to keep track of n-1 and n-2 in order to calculate and use these.
Quote:Why recursive function consumes more of processing time than loops?

Calling a function takes time and requires also resources. Iterating over something is faster than calling.
A call of a function is for a human very short, but if you do this one million times, you can recognize the difference.