Optimizing Performance

Pint can impose a significant performance overhead on computationally-intensive problems. The following are some suggestions for getting the best performance.

Note

Examples below are based on the IPython shell (which provides the handy %timeit extension), so they will not work in a standard Python interpreter.

Use magnitudes when possible

It’s significantly faster to perform mathematical operations on magnitudes (even though your’e still using pint to retrieve them from a quantity object).

In [1]: from pint import UnitRegistry

In [2]: ureg = UnitRegistry()

In [3]: q1 =ureg('1m')

In [5]: q2=ureg('2m')

In [6]: %timeit (q1-q2)
100000 loops, best of 3: 7.9 µs per loop

In [7]: %timeit (q1.magnitude-q2.magnitude)
1000000 loops, best of 3: 356 ns per loop

This is especially important when using pint Quantities in conjunction with an iterative solver, such as the brentq method from scipy:

In [1]: from scipy.optimize import brentq

In [2]: def foobar_with_quantity(x):
            # find the value of x that equals q2

            # assign x the same units as q2
            qx = ureg(str(x)+str(q2.units))

            # compare the two quantities, then take their magnitude because
            # brentq requires a dimensionless return type
            return (qx - q2).magnitude

In [3]: def foobar_with_magnitude(x):
            # find the value of x that equals q2

            # don't bother converting x to a quantity, just compare it with q2's magnitude
            return x - q2.magnitude

In [4]: %timeit brentq(foobar_with_quantity,0,q2.magnitude)
1000 loops, best of 3: 310 µs per loop

In [5]: %timeit brentq(foobar_with_magnitude,0,q2.magnitude)
1000000 loops, best of 3: 1.63 µs per loop

Bear in mind that altering computations like this loses the benefits of automatic unit conversion, so use with care.

A safer method: wrapping

A better way to use magnitudes is to use pint’s wraps decorator (See Wrapping and checking functions). By decorating a function with wraps, you pass only the magnitude of an argument to the function body according to units you specify. As such this method is safer in that you are sure the magnitude is supplied in the correct units.

In [1]: import pint

In [2]: ureg = pint.UnitRegistry()

In [3]: import numpy as np

In [4]: def f(x, y):
              return (x - y) / (x + y) * np.log(x/y)

In [5]: @ureg.wraps(None, ('meter', 'meter'))
         def g(x, y):
             return (x - y) / (x + y) * np.log(x/y)

In [6]: a = 1 * ureg.meter

In [7]: b = 1 * ureg.centimeter

In [8]: %timeit f(a, b)
1000 loops, best of 3: 312 µs per loop

In [9]: %timeit g(a, b)
10000 loops, best of 3: 65.4 µs per loop