numexpr
是一个用于高效数值计算的 Python 库,特别适合对大型数组进行快速的数学运算。它通过将 Python 表达式编译为优化的机器代码(利用多线程和向量化指令),显著提高计算性能。numexpr
是基于 NumPy 的扩展,通常与 NumPy 数组一起使用,适用于科学计算、数据分析和机器学习等场景。
以下是对 numexpr
库的详细说明和常见用法。
pip install numexpr
import numexpr
print(numexpr.__version__) # 示例输出: 2.10.1
注意:若需最大性能,安装时确保系统有 BLAS 库(如 MKL、OpenBLAS)。在 Linux 上,可通过
apt install libopenblas-dev
或类似命令安装。
numexpr
的核心功能是通过 evaluate
函数执行数学表达式,输入为字符串形式的表达式和 NumPy 数组。
使用 numexpr.evaluate
执行简单的数学运算:
import numpy as np
import numexpr as ne
# 创建 NumPy 数组
a = np.arange(1e6) # 100万个元素
b = np.arange(1e6)
# 使用 numexpr 计算
result = ne.evaluate("a + b")
print(result[:5]) # 输出: [0. 2. 4. 6. 8.]
说明:
ne.evaluate
接受字符串表达式(如 "a + b"
),自动识别变量 a
和 b
(NumPy 数组)。a + b
(纯 NumPy)更快。numexpr
支持以下操作:
+
, -
, *
, /
, **
(幂)、%
(模)。==
, !=
, <
, <=
, >
, >=
.&
(与)、|
(或)、~
(非)。sin
, cos
, tan
, exp
, log
, sqrt
, abs
, floor
, ceil
等。"2 * sin(a) + b ** 2"
.示例:
import numpy as np
import numexpr as ne
a = np.linspace(0, 1, 1000000)
b = np.linspace(1, 2, 1000000)
# 复杂表达式
result = ne.evaluate("2 * sin(a) + b ** 2")
print(result[:5])
通过 out
参数,numexpr
支持将结果存储到现有数组,减少内存分配:
import numpy as np
import numexpr as ne
a = np.arange(1e6)
b = np.arange(1e6)
out = np.empty_like(a) # 预分配输出数组
ne.evaluate("a + b", out=out)
print(out[:5]) # 输出: [0. 2. 4. 6. 8.]
说明:
out
参数指定输出数组,避免创建临时数组。numexpr
自动使用多线程,可通过 set_num_threads
控制线程数:
import numexpr as ne
# 查看当前线程数
print(ne.nthreads) # 默认等于 CPU 核心数
# 设置线程数
ne.set_num_threads(4)
print(ne.nthreads) # 输出: 4
# 测试多线程性能
a = np.arange(1e7)
b = np.arange(1e7)
result = ne.evaluate("a * b + sin(a)")
说明:
通过 local_dict
参数传递自定义变量:
import numpy as np
import numexpr as ne
a = np.arange(1e6)
c = 2.0
# 使用局部变量
result = ne.evaluate("a * c", local_dict={"a": a, "c": c})
print(result[:5]) # 输出: [0. 2. 4. 6. 8.]
说明:
local_dict
允许在表达式中使用非数组变量(如标量 c
)。numexpr
的性能优势主要体现在大型数组和复杂表达式上。以下是与纯 NumPy 的对比示例:
import numpy as np
import numexpr as ne
import time
a = np.arange(1e7)
b = np.arange(1e7)
# NumPy 计算
start = time.time()
result_np = 2 * np.sin(a) + b ** 2
print(f"NumPy time: {time.time() - start:.4f} seconds")
# numexpr 计算
start = time.time()
result_ne = ne.evaluate("2 * sin(a) + b ** 2")
print(f"numexpr time: {time.time() - start:.4f} seconds")
典型输出(视硬件而定):
NumPy time: 0.1500 seconds
numexpr time: 0.0500 seconds
分析:
numexpr
通过向量化、缓存优化和多线程,速度通常比 NumPy 快 2-10 倍。numexpr
的编译开销可能抵消性能优势。示例(Pandas 集成):
import pandas as pd
import numexpr as ne
df = pd.DataFrame({
"a": np.random.randn(1000000),
"b": np.random.randn(1000000)
})
# 使用 numexpr 加速 Pandas 计算
result = ne.evaluate("a + b", local_dict={"a": df["a"].values, "b": df["b"].values})
df["result"] = result
numexpr
支持预编译表达式以重复使用,减少编译开销:
import numexpr as ne
import numpy as np
# 编译表达式
expr = ne.NumExpr("a * b + sin(c)")
# 多次执行
a = np.arange(1e6)
b = np.arange(1e6)
c = np.linspace(0, 1, 1e6)
result = expr(a=a, b=b, c=c)
说明:
NumExpr
对象缓存编译结果,适合重复计算的场景。numexpr
支持复数(complex)、浮点数(float)、整数(int)等类型:
import numpy as np
import numexpr as ne
z = np.array([1 + 2j, 3 + 4j], dtype=complex)
result = ne.evaluate("abs(z)")
print(result) # 输出: [2.23606798 5. ]
检查 numexpr
的配置信息:
import numexpr as ne
print(ne.test()) # 输出环境信息,如 VML/MKL 可用性
示例输出:
numexpr version: 2.10.1
NumPy version: 1.26.4
Threads: 8
VML/MKL available: True
sin
)冲突。numexpr
比 NumPy 慢。numexpr
内部优化内存使用,但仍需注意大数组的内存分配。out
参数可减少内存占用。ne.set_num_threads(1)
禁用多线程。numexpr
和 NumPy 的版本,确保兼容性。以下是一个综合示例,展示 numexpr
在数据处理中的应用:
import numpy as np
import numexpr as ne
import time
# 创建大型数组
n = int(1e7)
a = np.random.randn(n)
b = np.random.randn(n)
c = np.linspace(0, 1, n)
# 复杂表达式计算
start = time.time()
result = ne.evaluate("2 * a * b + sin(c) + log1p(abs(b))")
print(f"numexpr time: {time.time() - start:.4f} seconds")
print(result[:5])
# 原地计算
out = np.empty(n)
start = time.time()
ne.evaluate("2 * a * b + sin(c) + log1p(abs(b))", out=out)
print(f"numexpr in-place time: {time.time() - start:.4f} seconds")
print(out[:5])
# 比较 NumPy
start = time.time()
result_np = 2 * a * b + np.sin(c) + np.log1p(np.abs(b))
print(f"NumPy time: {time.time() - start:.4f} seconds")
输出示例(视硬件而定):
numexpr time: 0.0450 seconds
[ 1.2345 -0.5678 2.3456 0.1234 1.7890]
numexpr in-place time: 0.0420 seconds
[ 1.2345 -0.5678 2.3456 0.1234 1.7890]
NumPy time: 0.1200 seconds
分析:
numexpr
在大型数组上显著优于 NumPy。numexpr
的设计受启发于 NumPy 和 ArrayFire,详见其文档。