In order to speed up code, there are a number of optimisations and shortcuts you can take. Here we focus on Python specifically.
The first step in speeding up code is profiling it. This can be done in a number of ways:
%%timeit
is Jupyter notebook magic to time the execution of a cell%%timeit
cell will not be storedline_profiler
packagepip
%load_ext line_profiler
Cython
Jupyter extension can be used to convert individual cells to Cython, so they are compiled before being run%load_ext Cython
%%Cython
%%Cython --annotate
, Jupyter will return a line-by-line analysis of your code highlighting which parts are closest to the Python and thus will take longest.py
files with cythonize
- see belowWhen working with arrays or lists and doing operations on them, it is recommended to use numpy
broadcasting instead of using for loops. This is because the numpy
library has been optimised and uses compiled C code to speed up execution.
For example, compare the below code blocks.
%%timeit
x = np.arange(1,5+1)
for i in range(len(x)):
x[i] = x[i]**2
# > 4.45 µs ± 972 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
x = np.arange(1,5+1)
x = np.pow(x,2)
# > 2.29 µs ± 350 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
As you can see, it takes half the time, which can be very significant.
However, note that numpy.array
s may not always be better than lists. Iteratively appending items is way faster for native python lists than for numpy arrays.
Cython is an extension of Python which is compiled into optimised C or C++ code, which can provide a significant speedup.
The steps are as follows:
.pyx
fileThe cythonize
shell command does steps 2-4 for you. If you’re working in a Jupyter notebook, the aforementioned %%Cython
magic works instead.
The type annotations must be done using C types, not Python types!
To type variables, use cdef
, for example
cdef double complex value # value is the variable name
To type function parameters and return values, use cpdef
:
cpdef int square(double complex x=5):
return x*x
It’s also possible to use numpy
in Cython. You need to import twice:
import numpy as np
cimport numpy as np
Then, use numpy as normal, with a couple of alterations:
cdef np.ndarray[long,ndim=2] my_array
for array definitionsAn alternative to all this rewriting is numba
, which is a JIT compiler which translates some Python and numpy
code into machine code.
To use, import numba
. The @numba.njit
decorator can be used before functions to immediately speed them up. However, the first call will be slow, as it will have to be compiled.
numba
also supports parallelisation. For example, to operate on all elements of a list independently, we need to do two things:
@numba.njit(parallel=True)
before the functionnumba.prange
to define the list(s) to be iterated overIf .csv
files get too large, they can be significantly compressed using a data structure known as a parquet.
Parquets are column-based, rather than row-based, and so are more suited to column operations.
You can achieve a significant speedup just with this change, for example:
import pandas as pd
df = pd.DataFrame(...)
df.to_parquet("data.parquet")
df = pd.from_parquet("data.parquet, engine="pyarrow")
# column operations are now much faster
Note that this requires the
pyarrow
package to be installed withpip install pyarrow
first.
functools.reduce
is a useful function to accumulate some function over a list.
For example:
xvals = [1,2,3,4,5,4,3,2,1]
max_val = 0
for i in xvals:
if i>max_val:
max_val = i
is equivalent to
from functools import reduce
reduce(max, xvals)