Overview

Teaching: 15 min
Exercises: 15 min
Questions
  • Why is my program so slow?

Objectives
  • Use a profiler to determine where a program is spending its time.

  • Explain the difference between sampling and instrumenting profilers.

We use computers because they’re faster than we are.

Get it right, then make it fast.

Distinguish per-call time, total time, and cumulative time.

Profilers necessarily distort execution (a little).

Installing a Profiler

The line_profiler module is not installed as part of the base Anaconda Python installation. You will need to use the conda package manager to install it:

$ conda install line_profiler

If you are using a different Python distribution, the line_profiler package can be installed through pip:

pip install line_profiler

Use a decorator to show which parts of the code you want to profile.

def primes(n):
    if n==2:
        return [2]
    elif n<2:
        return []

    s=list(range(3,n+1,2))
    mroot = n ** 0.5
    half=(n+1)//2-1
    i=0
    m=3

    while m <= mroot:
        if s[i]:
            j=(m*m-3)//2
            s[j]=0
            while j<half:
                s[j]=0
                j+=m
        i=i+1
        m=2*i+3
    return [2]+[x for x in s if x]

primes(100)
@profile
def primes(n):
    ...as before...

Take a Guess

Before going to the next step, try to guess where this code is going to spend most of its time. Which lines take longest to run individually and cumulatively?

Use kernprof to run from the command line and collect profile data.

Wrote profile results to primes.py.lprof
Timer unit: 1e-06 s

Total time: 0.000245 s
File: primes.py
Function: primes at line 1

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
     1                                           @profile
     2                                           def primes(n):
     3         1            8      8.0      3.3      if n==2:
     4                                                   return [2]
     5         1            2      2.0      0.8      elif n<2:
     6                                                   return []
     7
     8         1           11     11.0      4.5      s=range(3,n+1,2)
     9         1           36     36.0     14.7      mroot = n ** 0.5
    10         1            2      2.0      0.8      half=(n+1)/2-1
    11         1            1      1.0      0.4      i=0
    12         1            1      1.0      0.4      m=3
    13
    14         5            8      1.6      3.3      while m <= mroot:
    15         4            4      1.0      1.6          if s[i]:
    16         3            4      1.3      1.6              j=(m*m-3)/2
    17         3            4      1.3      1.6              s[j]=0
    18        31           33      1.1     13.5              while j<half:
    19        28           31      1.1     12.7                  s[j]=0
    20        28           30      1.1     12.2                  j+=m
    21         4            4      1.0      1.6          i=i+1
    22         4            5      1.2      2.0          m=2*i+3
    23        50           61      1.2     24.9      return [2]+[x for x in s if x]

Change and test one thing at a time.

Try It

Replace the square root computation with math.sqrt and compare the results from the previous profiler run.

Calculating Pi

  1. Write a program that calculates π using some approximation.
  2. Profile it.
  3. Try to make it faster.

An example python script is given in profiling_pi.py.

Profiling within IPython

Loading line_profiler

line_profiler has to be loaded into the IPython/Jupyter notebook by running:

`%load_ext line_profiler`

There are multiple ways to profile code within the IPython terminal depending how detailed you want to be These magic commands also work in Jupyter Notebooks

CPU times: user 21 µs, sys: 6 µs, total: 27 µs
Wall time: 23.8 µs
100000 loops, best of 3: 8.11 µs per loop
        4 function calls in 0.000 seconds

   Ordered by: internal time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.000    0.000 <ipython-input-26-4a302b84e1ce>:1(primes)
        1    0.000    0.000    0.000    0.000 <string>:1(<module>)
        1    0.000    0.000    0.000    0.000 {range}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

Total time: 0.000122 s File: Function: primes at line 1

Line # Hits Time Per Hit % Time Line Contents

 1                                           def primes(n):
 2         1            2      2.0      1.6      if n==2:
 3                                                   return [2]
 4         1            1      1.0      0.8      elif n<2:
 5                                                   return []
 6                                           
 7         1            4      4.0      3.3      s=range(3,n+1,2)
 8         1            2      2.0      1.6      mroot = n ** 0.5
 9         1            1      1.0      0.8      half=(n+1)//2-1
10         1            0      0.0      0.0      i=0
11         1            0      0.0      0.0      m=3
12                                           
13         5            4      0.8      3.3      while m <= mroot:
14         4            2      0.5      1.6          if s[i]:
15         3            3      1.0      2.5              j=(m*m-3)/2
16         3            4      1.3      3.3              s[j]=0
17        31           20      0.6     16.4              while j<half:
18        28           23      0.8     18.9                  s[j]=0
19        28           19      0.7     15.6                  j+=m
20         4            1      0.2      0.8          i=i+1
21         4            2      0.5      1.6          m=2*i+3
22        50           34      0.7     27.9      return [2]+[x for x in s if x] ~~~

Key Points