企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
# 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 利用了这一性能。