When writing Code Review answers, it becomes often necessary to measure how long the modified code takes vs how long the OP's code takes. I needed a nice way to visualize this as a function of the input, so I cobbled together the following script.
The plot_time
function takes a single function (of a single variable atm) and an iterable of inputs, performs multiple timings per input and finally plots it as an errorbar plot using matplotlib
(although usually the errorbars are too small to be seen).
The plot_times
function loops over an iterable of functions and adds some nice labels and a legend (containing the __name__
of each function tested).
from functools import partial
import timeit
import numpy as np
from matplotlib import pyplot
def plot_time(func, inputs, repeats, n_tests):
"""
Run timer and plot time complexity of `func` using the iterable `inputs`.
Run the function `n_tests` times per `repeats`.
"""
x, y, yerr = [], [], []
for i in inputs:
timer = timeit.Timer(partial(func, i))
t = timer.repeat(repeat=repeats, number=n_tests)
x.append(i)
y.append(np.mean(t))
yerr.append(np.std(t) / np.sqrt(len(t)))
pyplot.errorbar(x, y, yerr=yerr, fmt='-o', label=func.__name__)
def plot_times(functions, inputs, repeats=3, n_tests=1, file_name=""):
"""
Run timer and plot time complexity of all `functions`,
using the iterable `inputs`.
Run the functions `n_tests` times per `repeats`.
Adds a legend containing the labels added by `plot_time`.
"""
for func in functions:
plot_time(func, inputs, repeats, n_tests)
pyplot.legend()
pyplot.xlabel("Input")
pyplot.ylabel("Time [s]")
if not file_name:
pyplot.show()
else:
pyplot.savefig(file_name)
if __name__ == "__main__":
import math
import time
scale = 100.
def o_n(n):
time.sleep(n / scale)
def o_n2(n):
time.sleep(n**2 / scale)
def o_log(n):
time.sleep(math.log(n + 1) / scale)
def o_nlog(n):
time.sleep(n * math.log(n + 1) / scale)
def o_exp(n):
time.sleep((math.exp(n) - 1) / scale)
plot_times([o_n, o_n2, o_log, o_nlog, o_exp],
np.linspace(0, 1.1, num=10), repeats=3)
Saving the figure can either be done manually, or by passing a file name.
The test code, that defines some functions for the most common complexity classes (\$\mathcal{O}(n), \mathcal{O}(n^2), \mathcal{O}(\log n), \mathcal{O}(n \log n), \mathcal{O}(\exp n)\$), produces this graphical output:
Any thoughts or recommendations are welcome. Especially I don't think there is a nice way around (implicitly) using global variables here (when generating a new graph matplotlib
automatically adds it to the current figure).