重要的重新编码:如何加速我的程序? Cython、numba、多处理和 numpy?
Non-trivial recoding: How to speed up my program? Cython, numba, multiprocessing and numpy?
我有(或正在开发)一个程序(一些配对交易策略),它执行以下操作:
- 检索位于 postgres 数据库中的较大数据集(财务数据:日期时间指数和约 100 只股票的股票价格)的子集。
- 清理数据(删除 NaN >30% 的股票)并计算 returns 和索引(相对于每只股票的第一次观察)
- 求出所有的股票对组合,计算相关性(其实有一些类似的度量,但是这里太重要了)
- 将相关性最高的对排序为最低的,或者只选择相关性 > 定义阈值的对,即 0.9
- 双向检查这些对中的每一对是否协整!并根据他们的测试值对他们进行排名
- 选择要交易的前 n 个,即 10 对,并根据移动平均线和标准计算一些信号
- 检索 "out-of-sample" window 并交易股票
- 在日志中记录每天的return(即在 5 天内)
- 计算一些统计数据
完成这 9 个步骤后,重新开始,检索另一个训练 window 并执行分析...
我的方法是 - 如果您看到更好的地方,请更正:
1. 从程序中提取尽可能多的功能
2. 通过多次训练和交易循环步骤 1-9 windows
和我提出的问题(受到论坛中许多主题的启发,即 How to make your python code run faster
- 如何确定我的代码的哪一部分可以 运行 并行?
- 不知何故,这对我来说似乎微不足道:将什么技术应用于 "re-write" 代码,以便它可以使用多处理?
- 也并不总是很明显:将循环重写为函数,有什么特定的角度可以经常看?
- “
numba.jit()
”所有函数有意义吗?
- 我应该将所有数据格式更改为
float64
吗?会发生什么不利情况? (目前它们是 "standard" 数字)
- 是否有任何清单可供我查看何时可以矢量化循环?
请道歉许多 - 相当概念性的 - 问题,但我认为,如果我能理解以上所有 "pain" 点,那将真正提高我的 "logical" 理解,这也会非常有益对于新 python 加入者。
Non-trivial 问题可以产生但简化的答案:
性能改进需要多多关注,~[man*decades]
...
简单地说,不要指望读几个例子就成为这方面的专家。
没有。 1: 糟糕的算法永远不会仅仅通过一些(半)自动转换来改进。智能 re-factoring 可能会在本机普通 python 代码(下面的示例)中实现 +100% 的性能提升,但是精心制作的代码,与 code-execution 设备的 close-to-silicon 属性相匹配将在其他方面展示这种努力,如所述的 code-improvement ~ +100% 性能提升结果一旦转换为 jit
编译单元,其性能几乎相同。这意味着 pre-mature 优化可能在进入精心设计的 high-performance 代码时变得毫无用处。至少你已经被警告过。
python
是一个很棒的工具,我喜欢它 almost-无限精确的数学。然而,同时追求极致精度和极致性能似乎更接近海森堡原则,而不是计算机科学家,而且粉丝们更愿意承认这一点。只是花很长的时间做这两件事才能够把它压缩成几句话的段落。
Q4:“numba.jit()
”所有函数有意义吗?
numba
是稳定code-base的好工具,让我们开始吧:
使用 numba.jit()
工具可以轻松 cherry-picked automated-transformations 的低垂果实。基准测试将帮助您 shave-off 几乎所有您的代码不需要的开销。
如果依赖code-elements,那还在进化的numba.jit()
code-transformers无法转码,你就完蛋了。由于 numba
是非常初始的版本,因此与 numba
一起工作,{ list | dict | class | ... }
是让代码(自动)转换得更接近硅的任何进一步梦想的杀手.此外,所有引用的函数都必须能够获得 numba.jit()
,所以几乎忘记了一些 high-level import
-ed code-base 使用 numba
很容易翻译,如果他们的原始代码没有系统地设计 numba
。
问题 5:我是否应该将我的数据的所有格式更改为 float64
?
会发生什么不利情况?(在他们是 "standard" 数字 )
float32
除了将 [SPACE]
中的 memory-footprint 的静态大小减半之外-域,一些主要缺点。
一些模块(通常是那些继承自 FORTRAN 数值求解器和类似遗产的模块)auto-convert 任何外部传递到它们本地 float64
副本的数据(所以 [SPACE]
和 [TIME]
惩罚增加,超出你的控制范围)。
最好在 [TIME]
域中增加 code-execution 惩罚,因为 non-aligned cell-boundaries 很昂贵(这深入 assembly-level 的代码和 CPU-instruction 集和 cache-hierarchies 以掌握该级别的所有细节)。
在下面的基准测试中 float32
上的执行速度 几乎慢了 3 倍 。
问题 6:是否有任何清单可供我查看何时可以矢量化循环?
Auto-vectorising 变形金刚不亚于 Nobel-prize 目标。
聪明灵巧的设计师可以进行一些调整。不要指望在这个域中有任何 low-hanging 成果用于任何更复杂的操作,而不是简单的广播或一些易于设计的 numpy 跨步技巧。
专业 code-tranformation 软件包很昂贵(有人必须支付许多 [man*years] 中收集的专业知识)并且通常只有在大规模部署时才能调整其投资回报率。
Q1:如何识别我的代码的哪一部分可以运行并行?
您很高兴不必将代码设计为 运行 真-[PARALLEL]
,而是 "just"-[CONCURRENT]
时尚。如果有人说并行,检查系统是否确实需要满足 true-[PARALLEL]
process-scheduling 的所有条件,在大多数情况下,"just"-[CONCURRENT]
调度就是这样演讲者要求(详细信息超出了本 post 的范围)。 Python GIL-stepped 锁定可防止除 sub-process-based 之外的任何工作流拆分,但要付出一定的代价,因此收获您的处理独立性,因为这将奖励您的意图 without paying any penalty of additional overhead add-on costs, if going against rules of the overhead-strict Amdahl's Law.
基准,基准,基准:
实际的 cost/effect 比率必须在 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]
提高性能
,通过代码-re-factoring 和 memory-alignment,下一步
通过 numba.jit()
:
下降到 -> 12 ~ 17 [us]
>>> 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
我有(或正在开发)一个程序(一些配对交易策略),它执行以下操作:
- 检索位于 postgres 数据库中的较大数据集(财务数据:日期时间指数和约 100 只股票的股票价格)的子集。
- 清理数据(删除 NaN >30% 的股票)并计算 returns 和索引(相对于每只股票的第一次观察)
- 求出所有的股票对组合,计算相关性(其实有一些类似的度量,但是这里太重要了)
- 将相关性最高的对排序为最低的,或者只选择相关性 > 定义阈值的对,即 0.9
- 双向检查这些对中的每一对是否协整!并根据他们的测试值对他们进行排名
- 选择要交易的前 n 个,即 10 对,并根据移动平均线和标准计算一些信号
- 检索 "out-of-sample" window 并交易股票
- 在日志中记录每天的return(即在 5 天内)
- 计算一些统计数据
完成这 9 个步骤后,重新开始,检索另一个训练 window 并执行分析...
我的方法是 - 如果您看到更好的地方,请更正:
1. 从程序中提取尽可能多的功能
2. 通过多次训练和交易循环步骤 1-9 windows
和我提出的问题(受到论坛中许多主题的启发,即 How to make your python code run faster
- 如何确定我的代码的哪一部分可以 运行 并行?
- 不知何故,这对我来说似乎微不足道:将什么技术应用于 "re-write" 代码,以便它可以使用多处理?
- 也并不总是很明显:将循环重写为函数,有什么特定的角度可以经常看?
- “
numba.jit()
”所有函数有意义吗? - 我应该将所有数据格式更改为
float64
吗?会发生什么不利情况? (目前它们是 "standard" 数字) - 是否有任何清单可供我查看何时可以矢量化循环?
请道歉许多 - 相当概念性的 - 问题,但我认为,如果我能理解以上所有 "pain" 点,那将真正提高我的 "logical" 理解,这也会非常有益对于新 python 加入者。
Non-trivial 问题可以产生但简化的答案:
性能改进需要多多关注,~[man*decades]
...
简单地说,不要指望读几个例子就成为这方面的专家。
没有。 1: 糟糕的算法永远不会仅仅通过一些(半)自动转换来改进。智能 re-factoring 可能会在本机普通 python 代码(下面的示例)中实现 +100% 的性能提升,但是精心制作的代码,与 code-execution 设备的 close-to-silicon 属性相匹配将在其他方面展示这种努力,如所述的 code-improvement ~ +100% 性能提升结果一旦转换为 jit
编译单元,其性能几乎相同。这意味着 pre-mature 优化可能在进入精心设计的 high-performance 代码时变得毫无用处。至少你已经被警告过。
python
是一个很棒的工具,我喜欢它 almost-无限精确的数学。然而,同时追求极致精度和极致性能似乎更接近海森堡原则,而不是计算机科学家,而且粉丝们更愿意承认这一点。只是花很长的时间做这两件事才能够把它压缩成几句话的段落。
Q4:“numba.jit()
”所有函数有意义吗?
numba
是稳定code-base的好工具,让我们开始吧:
使用 numba.jit()
工具可以轻松 cherry-picked automated-transformations 的低垂果实。基准测试将帮助您 shave-off 几乎所有您的代码不需要的开销。
如果依赖code-elements,那还在进化的numba.jit()
code-transformers无法转码,你就完蛋了。由于 numba
是非常初始的版本,因此与 numba
一起工作,{ list | dict | class | ... }
是让代码(自动)转换得更接近硅的任何进一步梦想的杀手.此外,所有引用的函数都必须能够获得 numba.jit()
,所以几乎忘记了一些 high-level import
-ed code-base 使用 numba
很容易翻译,如果他们的原始代码没有系统地设计 numba
。
问题 5:我是否应该将我的数据的所有格式更改为 float64
?
会发生什么不利情况?(在他们是 "standard" 数字 )
float32
除了将 [SPACE]
中的 memory-footprint 的静态大小减半之外-域,一些主要缺点。
一些模块(通常是那些继承自 FORTRAN 数值求解器和类似遗产的模块)auto-convert 任何外部传递到它们本地 float64
副本的数据(所以 [SPACE]
和 [TIME]
惩罚增加,超出你的控制范围)。
最好在 [TIME]
域中增加 code-execution 惩罚,因为 non-aligned cell-boundaries 很昂贵(这深入 assembly-level 的代码和 CPU-instruction 集和 cache-hierarchies 以掌握该级别的所有细节)。
在下面的基准测试中 float32
上的执行速度 几乎慢了 3 倍 。
问题 6:是否有任何清单可供我查看何时可以矢量化循环?
Auto-vectorising 变形金刚不亚于 Nobel-prize 目标。
聪明灵巧的设计师可以进行一些调整。不要指望在这个域中有任何 low-hanging 成果用于任何更复杂的操作,而不是简单的广播或一些易于设计的 numpy 跨步技巧。
专业 code-tranformation 软件包很昂贵(有人必须支付许多 [man*years] 中收集的专业知识)并且通常只有在大规模部署时才能调整其投资回报率。
Q1:如何识别我的代码的哪一部分可以运行并行?
您很高兴不必将代码设计为 运行 真-[PARALLEL]
,而是 "just"-[CONCURRENT]
时尚。如果有人说并行,检查系统是否确实需要满足 true-[PARALLEL]
process-scheduling 的所有条件,在大多数情况下,"just"-[CONCURRENT]
调度就是这样演讲者要求(详细信息超出了本 post 的范围)。 Python GIL-stepped 锁定可防止除 sub-process-based 之外的任何工作流拆分,但要付出一定的代价,因此收获您的处理独立性,因为这将奖励您的意图 without paying any penalty of additional overhead add-on costs, if going against rules of the overhead-strict Amdahl's Law.
基准,基准,基准:
实际的 cost/effect 比率必须在 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]
提高性能
,通过代码-re-factoring 和 memory-alignment,下一步
通过 numba.jit()
:
-> 12 ~ 17 [us]
>>> 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