This plays with optimizing a fibonacci generator function for speed.

Study loop unrolling.


In [1]:
from itertools import islice

First we start with straightforward fibonacci generator function.


In [2]:
def fibonacci():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

In [3]:
n = 45

In [4]:
known_good_output = tuple(islice(fibonacci(), n))
# known_good_output

In [5]:
%timeit sum(islice(fibonacci(), n))


100000 loops, best of 3: 7.78 µs per loop

Next, we unroll the loop. Note that there are no assignments that just move things around. There is no wasted motion inside the loop. It reminds me of the musical round Three Blind Mice.


In [6]:
def fibonacci():
    a, b = 0, 1
    while True:
        yield a
        c = a + b
        yield b
        a = b + c
        yield c
        b = c + a

assert(known_good_output == tuple(islice(fibonacci(), n)))

In [7]:
%timeit sum(islice(fibonacci(), n))


100000 loops, best of 3: 7.38 µs per loop

Next, we unroll the loop more and more to see if that makes the generator faster.


In [8]:
def fibonacci():
    a, b = 0, 1
    while True:
        yield a
        c = a + b
        yield b
        a = b + c
        yield c
        b = c + a
        yield a
        c = a + b
        yield b
        a = b + c
        yield c
        b = c + a

assert(known_good_output == tuple(islice(fibonacci(), n)))

In [9]:
%timeit sum(islice(fibonacci(), n))


100000 loops, best of 3: 7.11 µs per loop

In [10]:
def fibonacci():
    a, b = 0, 1
    yield a
    yield b
    while True:
        c = a + b
        yield c
        a = b + c
        yield a
        b = c + a
        yield b

assert(known_good_output == tuple(islice(fibonacci(), n)))

In [11]:
%timeit sum(islice(fibonacci(), n))


100000 loops, best of 3: 7.26 µs per loop

In [12]:
def fibonacci():
    a, b = 0, 1
    yield a
    yield b
    while True:
        c = a + b
        yield c
        a = b + c
        yield a
        b = c + a
        yield b
        c = a + b
        yield c
        a = b + c
        yield a
        b = c + a
        yield b

assert(known_good_output == tuple(islice(fibonacci(), n)))

In [13]:
%timeit sum(islice(fibonacci(), n))


100000 loops, best of 3: 7.22 µs per loop

In [14]:
def fibonacci():
    a, b = 0, 1
    yield a
    yield b
    while True:
        c = a + b
        yield c
        a = b + c
        yield a
        b = c + a
        yield b
        c = a + b
        yield c
        a = b + c
        yield a
        b = c + a
        yield b
        c = a + b
        yield c
        a = b + c
        yield a
        b = c + a
        yield b

assert(known_good_output == tuple(islice(fibonacci(), n)))

In [15]:
%timeit sum(islice(fibonacci(), n))


100000 loops, best of 3: 6.99 µs per loop

In [16]:
def fibonacci():
    a, b = 0, 1
    yield a
    yield b
    while True:
        c = a + b
        yield c
        a = b + c
        yield a
        b = c + a
        yield b
        c = a + b
        yield c
        a = b + c
        yield a
        b = c + a
        yield b
        c = a + b
        yield c
        a = b + c
        yield a
        b = c + a
        yield b
        c = a + b
        yield c
        a = b + c
        yield a
        b = c + a
        yield b

assert(known_good_output == tuple(islice(fibonacci(), n)))

In [17]:
%timeit sum(islice(fibonacci(), n))


100000 loops, best of 3: 6.86 µs per loop

I get significantly different results each time I run the cells in the notebook, so it is unclear how much loop unrolling is good.