.. _performance:


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).

.. doctest::

   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:

.. doctest::

    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 :ref:`wrapping`). 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.

.. doctest::

    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

.. _`brentq method`: http://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.brentq.html
