Efficiency - Wed, Oct 16

Measuring Efficiency

Memoization

Remember results that have been computed before:

def memo(f):
    cache = {}
    def memoized(n):
        if n not in cache:
            cache[n] = f(n)
        return cache[n]
    return memoized

Multiple arguments? No problem!

def memo(f):
    cache = {}
    def memoized(*n):
        if n not in cache:
            cache[n] = f(*n)
        return cache[n]
    return memoized

There is a built-in memoization decorator:

from functools import lru_cache
@lru_cache(None)

Exponentiation

One more multiplication lets us double the problem size:

def exp(b, n):
    if n == 0:
        return 1
    else:
        return b * exp(b, n - 1)

Here, doubling the input doubles the processing time. 1024x the input takes 1024x the time.

def exp_fast(b, n):
    if n == 0:
        return 1
    elif n % 2 == 0:
        return square(exp_fast(b, n // 2))
    else:
        return b * exp_fast(b, n - 1)

Here, doubling the input increases time by a constant time C. 1024x the input increases the time by 10 times C.

Orders of Growth

Quadratic Time

Incrementing n increases time by n times a constant.

Exponential Time

Incrementing n multiplies time by a constant.

Linear Time

Incrementing n increases time by a constant.

Logarithmic Time

Doubling n only increments time by constant.

Constant Time

Increasing n doesn't affect time.

Space

Which environment frames do we need to keep during evaluation?

At any moment, there is a set of active environments. All values and frames in active environments consume memory, but the memory occupied by other values and frames can be recycled. Python automatically takes care of this.

What are active environments?