重要的问题可以产生但简化的答案:
性能提升需要多多关注,~ [man*decades]
...
简而言之,不要期望阅读一些示例并成为这方面的专家。
第一:糟糕的算法永远不会仅仅通过一些(半)自动转换来改进。智能重构可能会在原生纯 python 代码(下面的示例)中提高 100% 的性能,但是精心设计的代码,匹配代码执行设备的接近硅的特性将从其他角度显示这种努力,作为上述〜+ 100%性能繁荣的代码改进,一旦转换为jit
编译单元,性能几乎相同。这意味着,在精心设计的高性能代码中,过早的优化可能会变得毫无用处。至少你已经被警告过了。
python
是一个很棒的工具,我喜欢它几乎无限精确的数学。然而,与计算机科学家和粉丝们更愿意承认的相比,同时兼顾极致精度和极致性能似乎更接近海森堡原理。只是花了很长时间在做这两件事,才能把它紧凑成几句话的段落。
numba.jit()
Q4:所有功能都“”有意义吗?
numba
是稳定代码库的好工具,所以让我们从它开始:
numba.jit()
自动化转换的低挂果实很容易用工具挑选出来。基准测试将帮助您减少代码不需要携带的几乎所有开销。
如果依赖于代码元素,那么仍在发展的numba.jit()
代码转换器无法转码,那么您就完成了。numba
从最初的版本开始就使用它,{ list | dict | class | ... }
对于任何进一步梦想将代码(自动)转换为更接近硅片的梦想都是杀手。此外,所有引用的函数都必须能够获得numba.jit()
,所以几乎忘记了使用一些易于翻译的高级import
代码库numba
,如果它们的原始代码没有系统地设计numba
的话。
Q5:我应该将我的数据的所有格式都更改为float64
?
会发生什么不利情况?(目前它们是“标准”数字)
float32
除了 -domain 中内存占用的静态大小减半之外[SPACE]
,还有一些主要缺点。
一些模块(通常是那些继承自 FORTRAN 数值求解器和类似遗产的模块)会自动将任何外部传递的数据转换为其本地float64
副本(因此两者[SPACE]
和[TIME]
惩罚都会增长,超出您的控制范围)。
最好期望在 -domain 中增加代码执行惩罚[TIME]
,因为未对齐的单元边界很昂贵(这深入到代码的汇编级别以及 CPU 指令集和缓存层次结构以掌握该级别的所有细节) .
在下面的基准测试中,执行速度可能会慢近 3 倍。float32
Q6:是否有任何清单可以让我看到何时可以对循环进行矢量化?
自动矢量化转换器不亚于诺贝尔奖的目标。
一些调整可以由聪明和方便的设计师完成。对于任何更复杂的操作,不要指望在这个领域有任何容易实现的成果,而不是微不足道的广播或一些易于设计的 numpy striding trics。
专业的代码转换包很昂贵(有人必须支付在许多 [人*年] 中收集的专业知识),并且通常只有在大规模部署时才能调整其投资回报率。
Q1:如何确定我的代码的哪一部分可以并行运行?
您很高兴不必将代码设计为以真正的方式运行[PARALLEL]
,而是以“公正”的[CONCURRENT]
方式运行。如果有人说并行,请检查系统是否确实需要满足真正的[PARALLEL]
进程调度的所有条件,在大多数情况下,“公正”的[CONCURRENT]
调度正是演讲者所要求的(细节超出了本文的范围) . Python GIL 分步锁定可防止任何基于子流程的工作流拆分,但这是有代价的,因此请收获您的处理独立性,因为这将奖励您的意图,而不会支付任何额外开销附加成本的惩罚,如果违背开销严格的阿姆达尔定律的规则。
基准,基准,基准:
实际的成本/效果比必须在相应版本的 python、numba、CPU / 缓存架构上进行验证,因此基准测试是确认任何改进的唯一方法(成本)。
下面的例子显示了一个简单的指数移动平均函数的保存,以几种或多或少的智能方式实现。
def plain_EMA_fromPrice( N_period, aPriceVECTOR ):
...
@numba.jit( "float32[:]( int32, float32[:] )" )
def numba_EMA_fromPrice_float32( N_period, aPriceVECTOR ):
...
@numba.jit( "float32[:]( int32, float32[:] )", nopython = True, nogil = True )
def numba_EMA_fromPrice_float32_nopython( N_period, aPriceVECTOR ):
...
@numba.jit( "float64[:]( int64, float64[:] )" )
def numba_EMA_fromPrice_float64( N_period, aPriceVECTOR ):
...
@numba.jit( "float64[:]( int64, float64[:] )", nopython = True, nogil = True )
def numba_EMA_fromPrice_float64_nopython( N_period, aPriceVECTOR ):
...
def plain_EMA_fromPrice2( N_period, aPriceVECTOR ):
...
@numba.jit( "float32[:]( int32, float32[:] )" )
def numba_EMA_fromPrice2_float32( N_period, aPriceVECTOR ):
...
@numba.jit( "float32[:]( int32, float32[:] )", nopython = True, nogil = True )
def numba_EMA_fromPrice2_float32_nopython( N_period, aPriceVECTOR ):
...
@numba.jit( "float64[:]( int64, float64[:] )" )
def numba_EMA_fromPrice2_float64( N_period, aPriceVECTOR ):
...
@numba.jit( "float64[:]( int64, float64[:] )", nopython = True, nogil = True )
def numba_EMA_fromPrice2_float64_nopython( N_period, aPriceVECTOR ):
...
通过代码重构和内存对齐来
提高性能,接下来是:710 [us] -> 160 [us]
-> 12 ~ 17 [us]
numba.jit()
>>> aPV_32 = np.arange( 100, dtype = np.float32 )
>>> aPV_64 = np.arange( 100, dtype = np.float32 )
>>> aClk.start();_ = plain_EMA_fromPrice( 18, aPV_32 );aClk.stop()
715L
723L
712L
722L
975L
>>> aClk.start();_ = plain_EMA_fromPrice( 18, aPV_64 );aClk.stop()
220L
219L
216L
193L
212L
217L
218L
215L
217L
217L
>>> aClk.start();_ = numba_EMA_fromPrice_float32( 18, aPV_32 );aClk.stop()
199L
15L
16L
16L
17L
13L
16L
12L
>>> aClk.start();_ = numba_EMA_fromPrice_float64( 18, aPV_64 );aClk.stop()
170L
16L
16L
16L
18L
14L
16L
14L
17L
>>> aClk.start();_ = numba_EMA_fromPrice_float64_nopython( 18, aPV_64 );aClk.stop()
16L
17L
17L
16L
12L
16L
14L
16L
15L
>>> aClk.start();_ = plain_EMA_fromPrice2( 18, aPV_32 );aClk.stop()
648L
654L
662L
648L
647L
>>> aClk.start();_ = plain_EMA_fromPrice2( 18, aPV_64 );aClk.stop()
165L
166L
162L
162L
162L
163L
162L
162L
>>> aClk.start();_ = numba_EMA_fromPrice2_float32( 18, aPV_32 );aClk.stop()
43L
45L
43L
41L
41L
42L
>>> aClk.start();_ = numba_EMA_fromPrice2_float64( 18, aPV_64 );aClk.stop()
17L
16L
15L
17L
17L
17L
12L
>>> aClk.start();_ = numba_EMA_fromPrice2_float64_nopython( 18, aPV_64 );aClk.stop()
16L
15L
15L
14L
17L
15L