Python numba guvectorize target=&x27;平行';慢于目标=';中央处理器';

Python numba guvectorize target=&x27;平行';慢于目标=';中央处理器';,python,parallel-processing,numba,numexpr,Python,Parallel Processing,Numba,Numexpr,我一直在尝试优化一段涉及大型多维数组计算的python代码。我用numba得到了违反直觉的结果。我运行的是MBP,2015年年中,2.5 GHz i7四核,操作系统10.10.5,python 2.7.11。考虑以下事项: import numpy as np from numba import jit, vectorize, guvectorize import numexpr as ne import timeit def add_two_2ds_naive(A,B,res):

我一直在尝试优化一段涉及大型多维数组计算的python代码。我用numba得到了违反直觉的结果。我运行的是MBP,2015年年中,2.5 GHz i7四核,操作系统10.10.5,python 2.7.11。考虑以下事项:

 import numpy as np
 from numba import jit, vectorize, guvectorize
 import numexpr as ne
 import timeit

 def add_two_2ds_naive(A,B,res):
     for i in range(A.shape[0]):
         for j in range(B.shape[1]):
             res[i,j] = A[i,j]+B[i,j]

 @jit
 def add_two_2ds_jit(A,B,res):
     for i in range(A.shape[0]):
         for j in range(B.shape[1]):
             res[i,j] = A[i,j]+B[i,j]

 @guvectorize(['float64[:,:],float64[:,:],float64[:,:]'],
    '(n,m),(n,m)->(n,m)',target='cpu')
 def add_two_2ds_cpu(A,B,res):
     for i in range(A.shape[0]):
         for j in range(B.shape[1]):
             res[i,j] = A[i,j]+B[i,j]

 @guvectorize(['(float64[:,:],float64[:,:],float64[:,:])'],
    '(n,m),(n,m)->(n,m)',target='parallel')
 def add_two_2ds_parallel(A,B,res):
     for i in range(A.shape[0]):
         for j in range(B.shape[1]):
             res[i,j] = A[i,j]+B[i,j]

 def add_two_2ds_numexpr(A,B,res):
     res = ne.evaluate('A+B')

 if __name__=="__main__":
     np.random.seed(69)
     A = np.random.rand(10000,100)
     B = np.random.rand(10000,100)
     res = np.zeros((10000,100))
我现在可以在各种功能上运行timeit:

%timeit add_two_2ds_jit(A,B,res)
1000 loops, best of 3: 1.16 ms per loop

%timeit add_two_2ds_cpu(A,B,res)
1000 loops, best of 3: 1.19 ms per loop

%timeit add_two_2ds_parallel(A,B,res)
100 loops, best of 3: 6.9 ms per loop

%timeit add_two_2ds_numexpr(A,B,res)
1000 loops, best of 3: 1.62 ms per loop

“并行”似乎甚至不需要使用单个核心的大部分,因为它在
top
中的使用表明python的“并行”cpu使用率为40%,而“cpu”cpu使用率为100%,numexpr的使用率为300%。

您的@guvectorize实现存在两个问题。首先,您正在@guvectorize内核中执行所有循环,因此实际上没有任何东西可供Numba并行目标并行化。@vectorize和@guvectorize在ufunc/gufunc中的广播维度上并行化。由于gufunc的签名是2D,而输入是2D,因此只有一个对内部函数的调用,这解释了您所看到的CPU使用率仅为100%

编写上述函数的最佳方法是使用常规ufunc:

@vectorize('(float64, float64)', target='parallel')
def add_ufunc(a, b):
    return a + b
然后在我的系统上,我看到这些速度:

%timeit add_two_2ds_jit(A,B,res)
1000 loops, best of 3: 1.87 ms per loop

%timeit add_two_2ds_cpu(A,B,res)
1000 loops, best of 3: 1.81 ms per loop

%timeit add_two_2ds_parallel(A,B,res)
The slowest run took 11.82 times longer than the fastest. This could mean that an intermediate result is being cached 
100 loops, best of 3: 2.43 ms per loop

%timeit add_two_2ds_numexpr(A,B,res)
100 loops, best of 3: 2.79 ms per loop

%timeit add_ufunc(A, B, res)
The slowest run took 9.24 times longer than the fastest. This could mean that an intermediate result is being cached 
1000 loops, best of 3: 2.03 ms per loop
(这是一个与您的OS X系统非常相似的系统,但使用的是OS X 10.11。)

尽管Numba的并行ufunc现在超过了numexpr(我看到,
add_ufunc
使用了大约280%的CPU),但它并没有打败简单的单线程CPU。我怀疑瓶颈是由于内存(或缓存)带宽造成的,但我还没有进行测量来检查这一点


一般来说,如果您对每个内存元素(比如说,余弦)进行更多的数学运算,那么并行ufunc目标将带来更多好处。

但是
guvectorize
的要点是,您定义的操作将应用于任何额外维度(这是并行完成的位)。您编写的代码本身不会被并行化。因此,如果您将
A
B
res
更改为形状
(1000010000100)
第三维的100个不同迭代将并行运行。谢谢,我发现我误解了用法。