非平凡的重新编码:如何加速我的程序?Cython、numba、多处理和 numpy?

永恒1

我有(或实际上正在开发)一个程序(一些配对交易策略),它执行以下操作:

  1. 检索位于 postgres 数据库中的较大数据(财务数据:约 100 只股票的日期时间指数和股票价格)的子集。
  2. 清理数据(删除 >30% NaN 的股票)并计算回报和指数化(相对于每只股票的第一次观察)
  3. 找到股票对的所有组合,计算相关性(实际上有些类似的度量,但在这里太重要了)
  4. 将相关性最高的对排序到最低或仅选择相关性 > 定义阈值的对,即 0.9
  5. 检查这些对中的每一个是否有协整,两种方式!并根据他们的测试值对他们进行排名
  6. 选择要交易的前 n 对,即 10 对,并根据移动平均线和标准计算一些信号
  7. 检索“样本外”窗口并交易股票
  8. 在日志中记录每天的回报(即在 5 天内)
  9. 计算一些统计数据

在这 9 个步骤之后,重新开始,检索另一个训练窗口并执行分析......

我的方法是 - 如果你看到更好的东西,请更正:
1. 从程序中提取尽可能多的函数
2. 通过多个训练和交易窗口循环步骤 1-9

以及我由此产生的问题(受到论坛中许多主题的启发,即如何使您的 python 代码运行得更快

  • 如何确定我的代码的哪一部分可以并行运行?
  • 不知何故,对我来说似乎微不足道:应用什么技术来“重写”代码,以便它可以使用多处理?
  • 也不总是很明显:将循环重写为函数,任何特定的角度总是要看的?
  • numba.jit()”所有功能都有意义吗?
  • 我应该将数据的所有格式更改为float64吗?会发生什么坏处?(目前它们是“标准”数字)
  • 是否有任何清单可以让我看到何时可以矢量化循环?

请对许多 - 相当概念性的 - 问题表示歉意,但我认为,如果我能理解上述所有“痛点”,它将真正提高我的“逻辑”理解,并且对新的 Python 加入者也非常有益。

用户3666197

非平凡的问题可以产生但简化的答案:

性能提升需要多多关注,~ [man*decades]...

简而言之,不要期望阅读一些示例并成为这方面的专家。

第一:糟糕的算法永远不会仅仅通过一些(半)自动转换来改进。智能重构可能会在原生纯 Python 代码(下面的示例)中提高 100% 的性能,但精心设计的代码,与代码执行设备的接近硅特性相匹配,将在其他方面显示出这种努力,作为上述 ~ +100% 性能繁荣的代码改进结果,一旦转换为jit编译单元,性能几乎相同这意味着过早的优化在进入精心设计的高性能代码时可能会变得毫无用处。至少你已经被警告过。

python是一个很棒的工具,我喜欢它几乎无限精确的数学。然而,与计算机科学家相比,同时实现极致精度和极致性能似乎更接近海森堡原理,而且粉丝们也更愿意承认。只是花了很长时间才能把它压缩成几个词段。


Q4:“ numba.jit()”所有功能都有意义吗?

numba 是稳定代码库的好工具,让我们从它开始:

自动化转型的悬而未决的果实很容易用numba.jit()工具挑选出来基准测试将帮助您削减几乎所有代码不需要的开销。

如果取决于代码元素,仍在发展的numba.jit()代码转换器无法转码,那么您就完成了。numba自从它是最初的版本以来就开始使用它,{ list | dict | class | ... }对于让代码(自动)转换更接近硅片的任何进一步梦想来说都是杀手。此外,所有引用的函数都必须能够 get numba.jit(),所以几乎忘记了import使用 容易翻译的一些高级代码库numba,如果它们的原始代码没有系统地设计numba


Q5:我应该将我的数据的所有格式都更改为float64吗?
会发生什么坏处?(目前它们是“标准”数字)

float32除了将[SPACE]域中内存占用的静态大小减半外,还有一些主要缺点。

一些模块(通常是那些继承自 FORTRAN 数值求解器和类似遗产的模块)自动将任何外部传递的数据转换为它们的本地float64副本(因此[SPACE][TIME]惩罚都会增加,超出您的控制范围)。

更好地期待 -[TIME]域中增加的代码执行惩罚,因为未对齐的单元边界很昂贵(这深入到代码的汇编级别以及 CPU 指令集和缓存层次结构以掌握该级别的所有细节) .

在下面的基准测试中,可能会看到执行速度降低了近 3 倍float32


Q6:是否有任何清单可以让我看到何时可以矢量化循环?

自动矢量化变压器不亚于诺贝尔奖目标。

一些调整可以由聪明而方便的设计师完成。除了微不足道的广播或一些易于设计的 numpy 跨步技巧之外,不要指望在这个领域有任何更复杂的操作可以实现。

专业的代码转换包很昂贵(有人必须支付在许多 [man*years] 中收集的专业知识)并且通常只有在大规模部署时才能调整他们的 ROI。


Q1:如何确定我的代码的哪一部分可以并行运行?

您很高兴不必将代码设计为以真正的方式运行[PARALLEL],而是以“公正”的[CONCURRENT]方式运行。如果有人说并行,请检查系统是否确实需要满足真正[PARALLEL]进程调度的所有条件,在大多数情况下,“只是”[CONCURRENT]调度正是演讲者所要求的(详细信息超出了本文的范围) . Python GIL-stepped 锁定可防止任何基于子流程的工作流拆分,但需要付出代价,因此获得处理的独立性,因为这将奖励您的意图,而无需支付任何额外开销附加成本的惩罚,如果违反开销严格的阿姆达尔定律的规则。


基准、基准、基准:

实际成本/效果比必须在各自版本的 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],通过代码重新分解和内存对齐,接下来
DOWNTO-> 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

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章