Benchmarking, profiling and optimizing (II)

Profiling

Scalene

Scalene is a sampling profiler. In addition to timings , it can also give insight into:

  • CPU time spent in Python (interpreted), native (compiled) and system function calls

  • Memory usage and copy

  • GPU utilization

  • Memory leak detection

Moreover, it adds minimal overhead due to profiling. The downside is the results are less reproducible, because it is a sampling profiler.

Scalene can be used as a CLI tool, or using IPython magic or in Web interface as an interactive widget. Here are some examples profiling walk.py with Scalene.

CLI tool

$ scalene --cli walk.py

IPython magic

This allows for profiling a specific function. For example to profile just walk, we do as follows:

In [1]: %load_ext scalene

In [2]: %run walk.py

In [3]: %scrun --cli walk(n)

Gives the following output:

SCRUN MAGIC
                              /home/ashwinmo/Sources/enccs/hpda-python/content/example/walk.py: % of time = 100.00% (1.933s) out of 1.933s.
       β•·       β•·       β•·       β•·       β•·
       β”‚Time   │–––––– │–––––– │–––––– β”‚
  Line β”‚Python β”‚native β”‚system β”‚GPU    β”‚/home/ashwinmo/Sources/enccs/hpda-python/content/example/walk.py
╺━━━━━━┿━━━━━━━┿━━━━━━━┿━━━━━━━┿━━━━━━━┿━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╸
     1 β”‚       β”‚       β”‚       β”‚       β”‚"""A 1-D random walk.
     2 β”‚       β”‚       β”‚       β”‚       β”‚
     3 β”‚       β”‚       β”‚       β”‚       β”‚See also:
     4 β”‚       β”‚       β”‚       β”‚       β”‚- https://lectures.scientific-python.org/intro/numpy/auto_examples/plot_randomwalk.html
     5 β”‚       β”‚       β”‚       β”‚       β”‚
     6 β”‚       β”‚       β”‚       β”‚       β”‚"""
     7 β”‚       β”‚       β”‚       β”‚       β”‚import numpy as np
     8 β”‚       β”‚       β”‚       β”‚       β”‚
     9 β”‚       β”‚       β”‚       β”‚       β”‚
    10 β”‚    6% β”‚       β”‚       β”‚       β”‚def step():
    11 β”‚       β”‚       β”‚       β”‚       β”‚    import random
    12 β”‚    7% β”‚   64% β”‚  13%  β”‚       β”‚    return 1.0 if random.random() > 0.5 else -1.0
    13 β”‚       β”‚       β”‚       β”‚       β”‚
    14 β”‚       β”‚       β”‚       β”‚       β”‚
    15 β”‚       β”‚       β”‚       β”‚       β”‚def walk(n: int, dx: float = 1.0):
    16 β”‚       β”‚       β”‚       β”‚       β”‚    """The for-loop version.
    17 β”‚       β”‚       β”‚       β”‚       β”‚
    18 β”‚       β”‚       β”‚       β”‚       β”‚    Parameters
    19 β”‚       β”‚       β”‚       β”‚       β”‚    ----------
    20 β”‚       β”‚       β”‚       β”‚       β”‚    n: int
    21 β”‚       β”‚       β”‚       β”‚       β”‚        Number of time steps
    22 β”‚       β”‚       β”‚       β”‚       β”‚
    23 β”‚       β”‚       β”‚       β”‚       β”‚    dx: float
    24 β”‚       β”‚       β”‚       β”‚       β”‚        Step size. Default step size is unity.
    25 β”‚       β”‚       β”‚       β”‚       β”‚
    26 β”‚       β”‚       β”‚       β”‚       β”‚    """
    27 β”‚       β”‚       β”‚       β”‚       β”‚    xs = np.zeros(n)
    28 β”‚       β”‚       β”‚       β”‚       β”‚
    29 β”‚       β”‚       β”‚       β”‚       β”‚    for i in range(n - 1):
    30 β”‚       β”‚       β”‚       β”‚       β”‚        x_new = xs[i] + dx * step()
    31 β”‚    7% β”‚       β”‚       β”‚       β”‚        xs[i + 1] = x_new
    32 β”‚       β”‚       β”‚       β”‚       β”‚
    33 β”‚       β”‚       β”‚       β”‚       β”‚    return xs
    34 β”‚       β”‚       β”‚       β”‚       β”‚
    35 β”‚       β”‚       β”‚       β”‚       β”‚
    36 β”‚       β”‚       β”‚       β”‚       β”‚def walk_vec(n: int, dx: float = 1.0):
    37 β”‚       β”‚       β”‚       β”‚       β”‚    """The vectorized version of :func:`walk` using numpy functions."""
    38 β”‚       β”‚       β”‚       β”‚       β”‚    import random
    39 β”‚       β”‚       β”‚       β”‚       β”‚    steps = np.array(random.sample([1, -1], k=n, counts=[10 * n, 10 * n]))
    40 β”‚       β”‚       β”‚       β”‚       β”‚
    41 β”‚       β”‚       β”‚       β”‚       β”‚    # steps = np.random.choice([1, -1], size=n)
    42 β”‚       β”‚       β”‚       β”‚       β”‚
    43 β”‚       β”‚       β”‚       β”‚       β”‚    dx_steps = dx * steps
    44 β”‚       β”‚       β”‚       β”‚       β”‚
    45 β”‚       β”‚       β”‚       β”‚       β”‚    # set initial condition to zero
    46 β”‚       β”‚       β”‚       β”‚       β”‚    dx_steps[0] = 0
    47 β”‚       β”‚       β”‚       β”‚       β”‚    # use cumulative sum to replicate time evolution of position x
    48 β”‚       β”‚       β”‚       β”‚       β”‚    xs = np.cumsum(dx_steps)
    49 β”‚       β”‚       β”‚       β”‚       β”‚
    50 β”‚       β”‚       β”‚       β”‚       β”‚    return xs
    51 β”‚       β”‚       β”‚       β”‚       β”‚
    52 β”‚       β”‚       β”‚       β”‚       β”‚
    53 β”‚       β”‚       β”‚       β”‚       β”‚if __name__ == "__main__":
    54 β”‚       β”‚       β”‚       β”‚       β”‚    n = 1_000_000
    55 β”‚       β”‚       β”‚       β”‚       β”‚    _ = walk(n)
    56 β”‚       β”‚       β”‚       β”‚       β”‚    _ = walk_vec(n)
    57 β”‚       β”‚       β”‚       β”‚       β”‚
       β”‚       β”‚       β”‚       β”‚       β”‚
╢──────┼───────┼───────┼───────┼───────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╴
       β”‚       β”‚       β”‚       β”‚       β”‚function summary for /home/ashwinmo/Sources/enccs/hpda-python/content/example/walk.py
    10 β”‚   14% β”‚   69% β”‚   9%  β”‚       β”‚step
    15 β”‚    7% β”‚       β”‚       β”‚       β”‚walk
       β•΅       β•΅       β•΅       β•΅

If you run the magic command in Jupyter you can use %scrun walk(n) instead and it should an output similar to the Web interface below.

Web interface

Running

$ scalene walk.py

opens up the following web app:

../_images/scalene_web.png