CS61A(20): Efficiency

This is the lecture note of CS61A - Lecture 20.

Measuring Efficiency

By measuring efficiency, we can understand how long our programs will take to run.

Let's review our first example of tree recursion.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
def fib(n):
"""The nth Fibonacci number.

>>> fib(20)
6765
"""
if n == 0 or n == 1:
return n
else:
return fib(n-2) + fib(n-1)


def count(f):
"""Return a counted version of f with a call_count attribute.

>>> def fib(n):
... if n == 0 or n == 1:
... return n
... else:
... return fib(n-2) + fib(n-1)
>>> fib = count(fib)
>>> fib(20)
6765
>>> fib.call_count
21891
"""
def counted(*args):
counted.call_count += 1
return f(*args)
counted.call_count = 0
return counted

Memoization

Memoization is a useful technique for speeding up the running time of a program.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def memo(f):
"""Memoize f.

>>> def fib(n):
... if n == 0 or n == 1:
... return n
... else:
... return fib(n-2) + fib(n-1)
>>> fib = count(fib)
>>> fib(20)
6765
>>> fib.call_count
21891
>>> counted_fib = count(fib)
>>> fib = memo(counted_fib)
>>> fib(20)
6765
>>> counted_fib.call_count
21
>>> fib(35)
9227465
>>> counted_fib.call_count
36
"""
cache = {}
def memoized(n):
if n not in cache:
cache[n] = f(n)
return cache[n]
return memoized

Exponentiation

Let's look at another example.

  • Implement the exponential function in two different ways, one of which is more efficient than the other.

Orders of Growth

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Overlap

def overlap(a, b):
"""Count the number of items that appear in both a and b.

>>> overlap([3, 5, 7, 6], [4, 5, 6, 5])
3
"""
count = 0
for item in a:
for other in b:
if item == other:
count += 1
return count

Notation

Big-O describes the upper bound for the time it takes for a function to run.

Big-Theta desribes both a lower and a upper bound.

Space

Space, or memory, is another resource that get consumed by programs as they execute. So you need also worry about it.

Consumption of space is taken by values. It also gets taken up by frames, so you need to know how many frames exist because of different function calls in your program at the same time.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
def count_frames(f):
"""Return a counted version of f with a max_count attribute.

>>> def fib(n):
... if n == 0 or n == 1:
... return n
... else:
... return fib(n-2) + fib(n-1)
>>> fib = count_frames(fib)
>>> fib(20)
6765
>>> fib.open_count
0
>>> fib.max_count
20
>>> fib(25)
75025
>>> fib.max_count
25
"""
def counted(n):
counted.open_count += 1
counted.max_count = max(counted.max_count, counted.open_count)
result = f(n)
counted.open_count -= 1
return result
counted.open_count = 0
counted.max_count = 0
return counted

🎃 You can learn CS61B and CS170 to get more details about efficiency.