使用numba的向量化函数的并行执行速度



几年前,我用numba编写了一个数学库,用于对大型元素数组进行矢量化操作(例如:将两个矩阵数组相乘)。最近,我开始尝试并行性(当时还不存在的特性)。这样做,速度有所提高,但没有我预期的那么多。

为了进一步理解,我写了这个基本的例子:

import numpy as np
from numba import njit, prange, float64
@njit(float64[:] (float64[:], float64[:]), parallel=True)
def array_sum(array0, array1):
"""
Sum elements of array0 to array1, similar to doing + on two numpy arrays
"""
result = np.empty(array0.shape)
for i in prange(array0.shape[0]):
result[i] = array0[i] + array1[i]
return result

然后用:

测试
import time
v0 = np.random.random(10**7) # 10 million floats
v1 = np.random.random(10**7) # 10 million floats
array_sum(v0,v1) # run this once so the code compiles
#--- test start ---#
start_time = time.time()
array_sum(v0,v1) # do the work
end_time = time.time()
delta = end_time-start_time
print (delta) 
在我的机器上,我得到以下结果:
# direct numpy array0+array1 ---> 0.016865015029907227
# parallel=False             ---> 0.016468048095703125
# parallel=True              ---> 0.010709047317504883

parallel=False的速度我得到匹配numpy的,这是我所期望的。对于parallel=True,我得到了~1.54倍的速度提高,这很好,但是对于这样一个简单的操作,我期望在我的8核机器上有更大的影响。

是否可以更有效地使用并行性来执行类似于上面示例的向量化操作?

prange使用多线程如果使用parallel=True,那么目标机器的多个核心(如果可用)

也就是说,你的计算是内存绑定. 对于大型数组,需要从RAM读写数组,RAM是一个共享资源。因此,几个内核通常足以使RAM饱和。(这种情况在未来可能不会好转,因为过去30年的情况越来越糟)。在PC上尤其如此。在服务器上,由于NUMA

,这通常有点复杂。架构。提高可伸缩性的唯一方法是使代码的内存限制更少。例如,可以对进行操作。在L1/L2cache而不是在RAM中(即。更好的内存局部性)。但这需要计算的不仅仅是两个数组的和。实际上,当你想要合并几个基本操作时,这是很有用的。还要注意,就地求和在大多数体系结构(通常是具有写分配缓存策略的体系结构,如x86-64)上可以更快。

如果我正确阅读了文档https://numba.pydata.org/numba-doc/dev/user/parallel.html,那么并行选项不会利用您的多个内核:它似乎主要生成SIMD (AVX,诸如您所拥有的)指令。在这种情况下,你的操作不太可能被加快太多,因为它的带宽是有限的,所以没有什么可获得的。