Logo

A performant, verbose and generic physical unit system.

Static Badge Static Badge

© 2026 Patrick Müller. All rights reserved.

Benchmark

Output of the benchmark described in this tutorial:

init(pint): 2.75 s
init(qunits): 0.11 s
Speedup: 24.52x

inplace(pint): 3.54 s
inplace(qunits): 0.27 s
Speedup: 12.99x

units(pint): 2.65 s
units(qunits): 2.08 s
Speedup: 1.27x

array_ops(pint): 1.24 s
array_ops(qunits): 0.86 s
Speedup: 1.43x

conversion(pint): 1.40 s
conversion(qunits): 0.06 s
Speedup: 23.58x

qunits dimension cache size: 26
qunits unit cache size: 202

This tutorial shows benchmark test of qunits compared to pint for different operations. The tests are split into five categories: initialization, standard arithmetics, unit creation, array operations, and unit conversion. First, we import the necessary modules and set up the unit registries.

import os
import time

import numpy as np
from pint import UnitRegistry

from qunits import Quantity, u
from qunits.dimension import _dimension_cache
from qunits.unit import _unit_cache

pintcache = os.path.join(os.path.dirname(__file__), "__pintcache__")
p = UnitRegistry(cache_folder=pintcache)

Next, we define the benchmark functions for each category. Each function takes the name of the library being tested, the unit registry, and the number of iterations to perform. The functions measure the time taken to perform a specific operation repeatedly and print the results.

def bench_init(name, ureg, n=100_000):
    m = ureg.m
    mm = ureg.mm

    t0 = time.perf_counter()
    for _ in range(n):
        _ = (3 * m) + (4 * mm)  # type: ignore

    dt = time.perf_counter() - t0
    print(f"init({name}): {dt:.2f} s")
    return dt


def bench_inplace(name, ureg, n=100_000):
    m = ureg.m
    mm = ureg.mm

    a = 3 * m
    b = 4 * mm

    t0 = time.perf_counter()
    for _ in range(n):
        a += b
        a -= b
        a *= b
        a /= b

    dt = time.perf_counter() - t0
    print(f"inplace({name}): {dt:.2f} s")
    return dt


def bench_units(name, ureg, n=1_000_000):
    m = ureg.m
    s = ureg.s

    t0 = time.perf_counter()
    for _ in range(n):
        _ = m / s
        _ = m * s

    dt = time.perf_counter() - t0
    print(f"units({name}): {dt:.2f} s")
    return dt


def bench_array_ops(name, ureg, q, n=200):
    arr = np.ones(1_000_000)
    a = q(arr, ureg.m)
    b = q(arr, ureg.mm)

    t0 = time.perf_counter()
    for _ in range(n):
        _ = a + b
        _ = a - b
        _ = a * b
        _ = a / b

    dt = time.perf_counter() - t0
    print(f"array_ops({name}): {dt:.2f} s")
    return dt


def bench_conversion(name, ureg, q, n=100_000):
    m = ureg.m
    mm = ureg.mm
    a = q(5.0, mm)

    t0 = time.perf_counter()
    for _ in range(n):
        _ = a.to(m)
        _ = a.m_as(m)

    dt = time.perf_counter() - t0
    print(f"conversion({name}): {dt:.2f} s")
    return dt

Finally, we run the benchmark tests for each category and print the results.

n_samples = 100_000
dt_pint = bench_init("pint", p, n=n_samples)
dt_qunits = bench_init("qunits", u, n=n_samples)
print(f"Speedup: {dt_pint / dt_qunits:.2f}x\n")

n_samples = 100_000
dt_pint = bench_inplace("pint", p, n=n_samples)
dt_qunits = bench_inplace("qunits", u, n=n_samples)
print(f"Speedup: {dt_pint / dt_qunits:.2f}x\n")

n_samples = 1_000_000
dt_pint = bench_units("pint", p, n=n_samples)
dt_qunits = bench_units("qunits", u, n=n_samples)
print(f"Speedup: {dt_pint / dt_qunits:.2f}x\n")

n_samples = 200
dt_pint = bench_array_ops("pint", p, p.Quantity, n=n_samples)
dt_qunits = bench_array_ops("qunits", u, Quantity, n=n_samples)
print(f"Speedup: {dt_pint / dt_qunits:.2f}x\n")

n_samples = 100_000
dt_pint = bench_conversion("pint", p, p.Quantity, n=n_samples)
dt_qunits = bench_conversion("qunits", u, Quantity, n=n_samples)
print(f"Speedup: {dt_pint / dt_qunits:.2f}x\n")

print(f"qunits dimension cache size: {len(_dimension_cache)}")
print(f"qunits unit cache size: {len(_unit_cache)}")