在NumPy数组上进行迭代时,Numba似乎比Cython快得多。
我可能会缺少哪些Cython优化?
这是一个简单的例子:
import numpy as np
def f(arr):
res=np.zeros(len(arr))
for i in range(len(arr)):
res[i]=(arr[i])**2
return res
arr=np.random.rand(10000)
%timeit f(arr)
输出:每个循环4.81 ms±72.2 µs(平均±标准偏差,共运行7次,每个循环100个循环)
%load_ext cython
%%cython
import numpy as np
cimport numpy as np
cimport cython
from libc.math cimport pow
#@cython.boundscheck(False)
#@cython.wraparound(False)
cpdef f(double[:] arr):
cdef np.ndarray[dtype=np.double_t, ndim=1] res
res=np.zeros(len(arr),dtype=np.double)
cdef double[:] res_view=res
cdef int i
for i in range(len(arr)):
res_view[i]=pow(arr[i],2)
return res
arr=np.random.rand(10000)
%timeit f(arr)
输出:每个循环445 µs±5.49 µs(平均±标准偏差,共运行7次,每个循环1000次)
import numpy as np
import numba as nb
@nb.jit(nb.float64[:](nb.float64[:]))
def f(arr):
res=np.zeros(len(arr))
for i in range(len(arr)):
res[i]=(arr[i])**2
return res
arr=np.random.rand(10000)
%timeit f(arr)
输出:每循环9.59 µs±98.8 ns(平均±标准偏差,共运行7次,每个循环100000次)
在此示例中,Numba比Cython快50倍。
作为赛顿的初学者,我想我缺少一些东西。
当然,在这种简单的情况下,使用NumPy square
向量化函数会更合适:
%timeit np.square(arr)
输出:每个循环5.75 µs±78.9 ns(平均值±标准偏差,共运行7次,每个循环100000次)
答案 0 :(得分:7)
正如@Antonio所指出的那样,使用pow
进行简单的乘法不是很明智,并且会导致相当大的开销:
因此,将pow(arr[i], 2)
替换为arr[i]*arr[i]
可以大大提高速度:
cython-pow-version 356 µs
numba-version 11 µs
cython-mult-version 14 µs
剩余的差异可能是由于编译器和优化级别之间的差异(在我的案例中为llvm vs MSVC)。您可能想使用clang来匹配numba的性能(例如,请参见此SO-answer)
为了使编译器更容易进行优化,应将输入声明为连续数组,即double[::1] arr
(请参阅this question,为什么它对矢量化很重要),请使用@cython.boundscheck(False)
(使用选项-a
可以看到较少的黄色),还可以添加编译器标志(即-O3
,-march=native
或类似标记,具体取决于您的编译器以启用矢量化,请注意构建-默认情况下使用的标志会禁止某些优化,例如-fwrapv)。最后,您可能想用C语言编写Working-horse循环,使用标记/编译器的正确组合进行编译,然后使用Cython对其进行包装。
顺便说一句,通过将函数的参数键入为nb.float64[:](nb.float64[:])
会降低numba的性能-不再允许假定输入数组是连续的,从而排除了向量化的可能性。让numba检测类型(或将其定义为连续的类型,即nb.float64[::1](nb.float64[::1]
),您将获得更好的性能:
@nb.jit(nopython=True)
def nb_vec_f(arr):
res=np.zeros(len(arr))
for i in range(len(arr)):
res[i]=(arr[i])**2
return res
可带来以下改进:
%timeit f(arr) # numba version
# 11.4 µs ± 137 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit nb_vec_f(arr)
# 7.03 µs ± 48.9 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
正如@ max9111指出的那样,我们不必使用零初始化结果数组,但是可以使用np.empty(...)
而不是np.zeros(...)
-此版本甚至胜过了numpy的{{1} }
我的机器上不同方法的性能如下:
np.square()