# 1.13。性能提示
> 原文: [http://numba.pydata.org/numba-doc/latest/user/performance-tips.html](http://numba.pydata.org/numba-doc/latest/user/performance-tips.html)
这是 Numba 中功能的简短指南,可以帮助您从代码中获得最佳性能。使用了两个例子,两者都完全是人为的,纯粹出于教学原因而存在,以激发讨论。第一个是三角恒等式`cos(x)^2 + sin(x)^2`的计算,第二个是矢量的简单元素方形平方根,它是求和的减少。所有性能数字仅供参考,除非另有说明,否则选自在`np.arange(1.e7)`输入的英特尔`i7-4790` CPU(4 个硬件线程)上运行。
注意
实现高性能代码的一种合理有效的方法是使用实际数据分析运行的代码,并使用它来指导性能调优。这里提供的信息是为了展示功能,而不是作为规范指导!
## 1.13.1。没有 Python 模式与对象模式
一个常见的模式是用`@jit`来装饰函数,因为这是 Numba 提供的最灵活的装饰器。 `@jit`本质上包含两种编译模式,首先它将尝试在没有 Python 模式下编译装饰函数,如果失败,它将再次尝试使用对象模式编译函数。虽然在对象模式下使用循环可以提高性能,但是在无 python 模式下编译函数确实是获得良好性能的关键。为了使得只使用没有 python 模式,并且如果编译失败,则引发异常,可以使用装饰器`@njit`和`@jit(nopython=True)`(为方便起见,第一个是第二个的别名)。
## 1.13.2。循环
虽然 NumPy 在矢量运算的使用方面已经形成了一个强有力的习惯,但 Numba 对循环也非常满意。对于熟悉 C 或 Fortran 的用户,以这种方式编写 Python 在 Numba 中可以正常工作(毕竟,LLVM 在编译 C 谱系语言时有很多用处)。例如:
```py
@njit
def ident_np(x):
return np.cos(x) ** 2 + np.sin(x) ** 2
@njit
def ident_loops(x):
r = np.empty_like(x)
n = len(x)
for i in range(n):
r[i] = np.cos(x[i]) ** 2 + np.sin(x[i]) ** 2
return r
```
当用`@njit`修饰时,上面以几乎相同的速度运行,没有装饰器,矢量化功能的速度提高了几个数量级。
| 功能名称 | @njit | 执行时间处理时间 |
| --- | --- | --- |
| `ident_np` | 没有 | 0.581s |
| `ident_np` | 是 | 0.659s |
| `ident_loops` | 没有 | 25.2s |
| `ident_loops` | 是 | 0.670s |
## 1.13.3。 Fastmath
在某些类别的应用中,严格的 IEEE 754 合规性不那么重要。因此,可以放松一些数字严谨性,以获得额外的性能。在 Numba 中实现此行为的方法是使用`fastmath`关键字参数:
```py
@njit(fastmath=False)
def do_sum(A):
acc = 0.
# without fastmath, this loop must accumulate in strict order
for x in A:
acc += np.sqrt(x)
return acc
@njit(fastmath=True)
def do_sum_fast(A):
acc = 0.
# with fastmath, the reduction can be vectorized as floating point
# reassociation is permitted.
for x in A:
acc += np.sqrt(x)
return acc
```
| 功能名称 | 执行时间处理时间 |
| --- | --- |
| `do_sum` | 35.2 毫秒 |
| `do_sum_fast` | 17.8 毫秒 |
## 1.13.4。并行=真
如果代码包含可并行的操作([和支持](parallel.html#numba-parallel-supported)),Numba 可以编译一个版本,它将在多个本机线程上并行运行(没有 GIL!)。这种并行化是自动执行的,只需添加`parallel`关键字参数即可启用:
```py
@njit(parallel=True)
def ident_parallel(A):
return np.cos(x) ** 2 + np.sin(x) ** 2
```
执行时间如下:
| 功能名称 | 执行时间处理时间 |
| --- | --- |
| `ident_parallel` | 112 毫秒 |
存在`parallel=True`的此功能的执行速度约为 NumPy 等效值的 5 倍,是标准`@njit`的 6 倍。
Numba 并行执行也支持显式并行循环声明,类似于 OpenMP。为了表明应该并行执行循环,应该使用`numba.prange`函数,这个函数的行为类似于 Python `range`,如果没有设置`parallel=True`,它只是作为`range`的别名。用`prange`诱导的循环可用于令人尴尬的并行计算和减少。
重新考虑 reduce over sum 示例,假设无法按顺序累积总和是安全的,`n`中的循环可以通过使用`prange`来并行化。此外,在这种情况下可以毫无顾虑地添加`fastmath=True`关键字参数,因为已经通过使用`parallel=True`(因为每个线程计算部分和)已经进行了无序执行有效的假设。
```py
@njit(parallel=True)
def do_sum_parallel(A):
# each thread can accumulate its own partial sum, and then a cross
# thread reduction is performed to obtain the result to return
n = len(A)
acc = 0.
for i in prange(n):
acc += np.sqrt(A[i])
return acc
@njit(parallel=True, fastmath=True)
def do_sum_parallel_fast(A):
n = len(A)
acc = 0.
for i in prange(n):
acc += np.sqrt(A[i])
return acc
```
执行时间如下,`fastmath`再次提高性能。
| 功能名称 | 执行时间处理时间 |
| --- | --- |
| `do_sum_parallel` | 9.81 毫秒 |
| `do_sum_parallel_fast` | 5.37 毫秒 |
## 1.13.5。英特尔 SVML
英特尔提供了一个简短的矢量数学库(SVML),其中包含大量优化的超越函数,可用作编译器内在函数。如果环境中存在`icc_rt`包(或者 SVML 库只是可定位的!),那么 Numba 会自动配置 LLVM 后端以尽可能使用 SVML 内部函数。 SVML 提供每个内在函数的高精度和低精度版本,并且使用的版本通过使用`fastmath`关键字来确定。默认使用精度高于`1 ULP`的高精度,但如果`fastmath`设置为`True`,则使用内在函数的低精度版本(`4 ULP`内的答案)。
首先使用 conda 获取 SVML,例如:
```py
conda install -c numba icc_rt
```
从上面重新运行身份函数示例`ident_np`,使用`@njit`和/或不使用 SVML 的各种选项组合,得到以下性能结果(输入大小`np.arange(1.e8)`)。作为参考,仅使用 NumPy 在`5.84s`中执行的功能:
| `@njit` kwargs | SVML | 执行时间处理时间 |
| --- | --- | --- |
| `None` | 没有 | 5.95s |
| `None` | 是 | 2.26s |
| `fastmath=True` | 没有 | 5.97s |
| `fastmath=True` | 是 | 1.8 秒 |
| `parallel=True` | 没有 | 1.36s |
| `parallel=True` | 是 | 0.624s |
| `parallel=True, fastmath=True` | 没有 | 1.32s |
| `parallel=True, fastmath=True` | 是 | 0.576s |
很明显,SVML 显着提高了该功能的性能。在不存在 SVML 的情况下`fastmath`的影响是零,这是预期的,因为原始函数中没有任何东西可以从放宽数字严格性中受益。
## 1.13.6。线性代数
Numba 在没有 Python 模式的情况下支持大多数`numpy.linalg`。内部实现依赖于 LAPACK 和 BLAS 库来完成数值工作,并从 SciPy 获取必要函数的绑定。因此,要在 Numba 的`numpy.linalg`函数中实现良好的性能,必须使用针对优化良好的 LAPACK / BLAS 库构建的 SciPy。在 Anaconda 发行版的情况下,SciPy 是针对英特尔的 MKL 构建的,该版本经过高度优化,因此 Numba 利用了这一性能。
- 1. 用户手册
- 1.1。 Numba 的约 5 分钟指南
- 1.2。概述
- 1.3。安装
- 1.4。使用@jit 编译 Python 代码
- 1.5。使用@generated_jit 进行灵活的专业化
- 1.6。创建 Numpy 通用函数
- 1.7。用@jitclass 编译 python 类
- 1.8。使用@cfunc 创建 C 回调
- 1.9。提前编译代码
- 1.10。使用@jit 自动并行化
- 1.11。使用@stencil装饰器
- 1.12。从 JIT 代码 中回调到 Python 解释器
- 1.13。性能提示
- 1.14。线程层
- 1.15。故障排除和提示
- 1.16。常见问题
- 1.17。示例
- 1.18。会谈和教程
- 2. 参考手册
- 2.1。类型和签名
- 2.2。即时编译
- 2.3。提前编译
- 2.4。公用事业
- 2.5。环境变量
- 2.6。支持的 Python 功能
- 2.7。支持的 NumPy 功能
- 2.8。与 Python 语义的偏差
- 2.9。浮点陷阱
- 2.10。 Python 2.7 寿命终止计划
- 3. 用于 CUDA GPU 的 Numba
- 3.1。概述
- 3.2。编写 CUDA 内核
- 3.3。内存管理
- 3.4。编写设备功能
- 3.5。 CUDA Python 中支持的 Python 功能
- 3.6。支持的原子操作
- 3.7。随机数生成
- 3.8。设备管理
- 3.10。示例
- 3.11。使用 CUDA 模拟器 调试 CUDA Python
- 3.12。 GPU 减少
- 3.13。 CUDA Ufuncs 和广义 Ufuncs
- 3.14。共享 CUDA 内存
- 3.15。 CUDA 阵列接口
- 3.16。 CUDA 常见问题
- 4. CUDA Python 参考
- 4.1。 CUDA 主机 API
- 4.2。 CUDA 内核 API
- 4.3。内存管理
- 5. 用于 AMD ROC GPU 的 Numba
- 5.1。概述
- 5.2。编写 HSA 内核
- 5.3。内存管理
- 5.4。编写设备功能
- 5.5。支持的原子操作
- 5.6。代理商
- 5.7。 ROC Ufuncs 和广义 Ufuncs
- 5.8。示例
- 6. 扩展 Numba
- 6.1。高级扩展 API
- 6.2。低级扩展 API
- 6.3。示例:间隔类型
- 7. 开发者手册
- 7.1。贡献给 Numba
- 7.2。 Numba 建筑
- 7.3。多态调度
- 7.4。关于发电机的注意事项
- 7.5。关于 Numba Runtime 的注意事项
- 7.6。使用 Numba Rewrite Pass 获得乐趣和优化
- 7.7。实时变量分析
- 7.8。上市
- 7.9。模板注释
- 7.10。关于自定义管道的注意事项
- 7.11。环境对象
- 7.12。哈希 的注意事项
- 7.13。 Numba 项目路线图
- 8. Numba 增强建议
- 9. 术语表