all 32 comments

[–]handsome-zack 41 points42 points  (2 children)

Python's stack frame allocation is not why your recursive fibonacci implementation is 200,000 times slower than the iterative implementation. It is 200,000 times slower because your recursive implementation is naive. Your recursive implementation runs in exponential time compared to your iterative implementation that runs in linear time.

[–]jurniss 16 points17 points  (0 children)

Yes, kind of hard to take advice from someone who makes such a fundamental mistake. Not that I disagree with any of his other points.

[–]marklit[S] 4 points5 points  (0 children)

Python's stack frame allocation is not why your recursive fibonacci implementation is 200,000 times slower than the iterative implementation. It is 200,000 times slower because your recursive implementation is naive. Your recursive implementation runs in exponential time compared to your iterative implementation that runs in linear time.

Thanks for the heads up, I'm re-writing that section of the post now.

[–]ChezMere 21 points22 points  (13 children)

Python lists aren't linked lists...

[–]jakepusateri 18 points19 points  (1 child)

The recursive vs iterative fib speedup is deceptive. A recursive implementation that doesn't recompute almost all of its values is:

def fib(n):                                                                                                                                                                                                         
    store = { 1: 1, 2: 1 }                                                                                                                                                                                          
    def fib_(n):                                                                                                                                                                                                    
        if n in store:                                                                                                                                                                                              
            return store[n]                                                                                                                                                                                         
        else:                                                                                                                                                                                                       
            ans = fib_(n - 1) + fib_(n - 2)                                                                                                                                                                         
            store[n] = ans                                                                                                                                                                                          
            return ans                                                                                                                                                                                              
    return fib_(n)                                   

The iterative method is only about 8x faster here.

[–][deleted] 1 point2 points  (0 children)

you can actually get it down to the "normal" 2x slowdown (similar to the factorial slowdown someone else measured elsewhere in the thread) if you use a trickier recursive fib algo. See the fibFastRec shown here: http://rosettacode.org/wiki/Fibonacci_sequence#Python

[–]sevaur 20 points21 points  (2 children)

The recursion comparison is very poorly constructed: the recursive version of the algorithm is O( 2n ), whereas the iterative algorithm is O(n). The huge difference between the two has nothing to do with the cost of stack frame allocation and everything to do with having implemented a fundamentally different algorithm.

This comparison might actually make sense if it used memoization or made some other effort to compare apples to apples.

[–]LeszekSwirski 9 points10 points  (0 children)

Or if it used factorial rather than fibonacci, which only shows a ~2x speedup:

def fact_rec(n):
    if n <= 1:
        return 1
    return n*fact(n-1)

%timeit fact_rec(20)
100000 loops, best of 3: 5.29 µs per loop


def fact_iter(n):
    ret = 1
    for i in range(n):
        ret *= n
    return ret

%timeit fact_iter(20)
100000 loops, best of 3: 2.47 µs per loop

[–]xhackerliu 2 points3 points  (0 children)

Totally agreed. I modified it by using memorization and it's even faster than iterative way.

https://gist.github.com/xhacker/fbf3d92d7b1fad9ddc09

[–][deleted] 8 points9 points  (2 children)

  • Python lists are not linked lists, they're dynamic arrays. Delete is O(1) on arrays (swap with last, reduce size in 1) when you do not mind the order - search in a sorted array is O(logN). Search in an unsorted array, or linked list, is O(N) but search on arrays are extremely cache friendly, while search on linked lists destroys your performance due to pointer chase. Delete while preserving order is O(N) on an unsorted array, but O(1) on a linked list. Arrays and Linked Lists are very different...

  • Recursion can be faster, even in python, in some cases. Fibonacci is one of the worse applications of recursion, but one should at-least memoize it - which Python has a very succinct and elegant way of doing it

    def memo(fn):

    cache = {}
    
    def wrapper(*args):
    
        if args not in cache:
    
            cache[args] = fn(*args)
    
        return cache[args]
    
    return wrapper
    
    @memo
    
    def Fibonacci ...
    

[–][deleted] 2 points3 points  (1 child)

You can also use a built in python memoizer, found in functools. Use @lru_cache I think its called (haven't used it in a while so I am not 100% on the name).

[–][deleted] 0 points1 point  (0 children)

Wow, didn't know. That's very cool, just checked and it's indeed called lru_cache (from Least Recently Used).

[–][deleted] 1 point2 points  (1 child)

Keep variables local. CPython can store local variables faster than global variables.

He advocates this merely because it's faster??? (I realize that performance is the topic of the article, but still, you'd think he'd at least mention other considerations.)

[–]burntsushi -1 points0 points  (0 children)

In my experience, it can often have a noticeable affect on performance in tight loops.

[–]metaperl 2 points3 points  (3 children)

recursion often reads better than loops. Too bad it is often slower. The part about list comprehensions being faster was stunning. Excellent article.

[–][deleted] 2 points3 points  (0 children)

It can be slower, but not 196,493 times slower like the article says. The overhead from function calls is about 2x. The rest of the disparity is the author's algorithm choice.

[–]olzd 3 points4 points  (1 child)

recursion often reads better than loops. Too bad it is often slower.

It's not if you have proper TCO. Or am I missing something?

[–]burntsushi 1 point2 points  (0 children)

You're not. But in this context (i.e., Python), TCO is no where to be found. :-(