Fast numerical expression evaluator for NumPy
NumExpr is a Python library that provides a fast numerical expression evaluator for NumPy. It accelerates array operations by avoiding memory allocation for intermediate results, leading to better cache utilization and reduced memory access. It also leverages multi-threading to utilize multiple CPU cores and supports Intel's Math Kernel Library (MKL) for further performance gains, especially with transcendental functions. The current version is 2.14.1, and it maintains a regular release cadence.
Warnings
- gotcha Breaking down expressions into multiple NumPy operations that create intermediate arrays negates NumExpr's primary performance and memory benefits.
- gotcha NumExpr's internal casting rules and type promotion might differ slightly from NumPy's in specific cases (e.g., `int8`/`uint8` upcasting to `int32`, `uint32` to `int64`, and `abs()` on complex numbers returning a complex result).
- gotcha Incorrectly configuring threadpool settings (e.g., `NUMEXPR_MAX_THREADS`, `NUMEXPR_NUM_THREADS`, or `OMP_NUM_THREADS` environment variables) can lead to oversubscription and degraded performance, especially when combined with other parallel processing libraries or Python's threading.
- gotcha NumExpr introduces parsing and compilation overhead. For small arrays or very simple expressions, native NumPy operations can be faster due to this overhead.
- breaking NumExpr 2.10.0 introduced *experimental* support for NumPy 2.0.0. While intended for forward compatibility, users adopting NumPy 2.x may encounter edge cases or instabilities.
Install
-
pip install numexpr
Imports
- numexpr
import numexpr as ne
- evaluate
ne.evaluate('expression')
Quickstart
import numpy as np
import numexpr as ne
# Create large arrays for demonstrating performance benefits
a = np.arange(1_000_000, dtype=np.float64)
b = np.arange(1_000_000, 0, -1, dtype=np.float64)
# Evaluate a complex expression using numexpr
result_ne = ne.evaluate("sin(a) + arcsinh(a/b) + (a * b - 4.1 * a) > 2.5 * b")
print(result_ne)
print(f"Result type: {result_ne.dtype}")
# You can also set the number of threads dynamically
# print(f"Current numexpr threads: {ne.nthreads}")
# ne.set_num_threads(4)
# print(f"New numexpr threads: {ne.nthreads}")