Python 为什么Cython在迭代NumPy数组时比Numba慢得多?

Python 为什么Cython在迭代NumPy数组时比Numba慢得多?,python,numpy,cython,numba,Python,Numpy,Cython,Numba,在NumPy数组上迭代时,Numba似乎比Cython快得多。 我可能缺少哪些Cython优化 下面是一个简单的例子: 纯Python代码: 输出:每个回路4.81 ms±72.2µs(7次运行的平均值±标准偏差,每个100个回路) Cython代码(在Jupyter中): 输出:每个回路445µs±5.49µs(7次运行的平均±标准偏差,每个1000个回路) Numba代码: 输出:每个回路9.59µs±98.8 ns(7次运行的平均值±标准偏差,每个100000个回路) 在本例中,Nu

在NumPy数组上迭代时,Numba似乎比Cython快得多。
我可能缺少哪些Cython优化

下面是一个简单的例子:

纯Python代码: 输出:每个回路4.81 ms±72.2µs(7次运行的平均值±标准偏差,每个100个回路)


Cython代码(在Jupyter中): 输出:每个回路445µs±5.49µs(7次运行的平均±标准偏差,每个1000个回路)


Numba代码: 输出:每个回路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次)

正如@Antonio所指出的,使用
pow
进行简单乘法不是很明智,会导致相当大的开销:

因此,通过
arr[i]*arr[i]
替换
pow(arr[i],2)
会导致相当大的加速:

cython-pow-version        356 µs
numba-version              11 µs
cython-mult-version        14 µs
剩下的差异可能是由于编译器和优化级别之间的差异(在我的例子中是llvm和MSVC)。您可能希望使用clang来匹配numba性能(例如,请参见此示例)

为了使编译器更容易进行优化,您应该将输入声明为连续数组,即
double[::1]arr
(请参见为什么它对矢量化很重要),使用
@cython.boundscheck(False)
(使用选项
-a
,查看黄色是否较少)并添加编译器标志(例如,
-O3
-march=native
或类似的,具体取决于您的编译器,要启用矢量化,请注意默认情况下使用的生成标志,它可能会抑制某些优化,例如)。最后,您可能希望用C编写工作的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.zero(…)
——这个版本甚至比numpy的
np.square()

在我的机器上,不同方法的性能如下:

numba+vectorization+empty     3µs
np.square                     4µs
numba+vectorization           7µs
numba missed vectorization   11µs
cython+mult                  14µs
cython+pow                  356µs

你为什么不在cython代码中也使用arr[i]**2呢?我认为一个可能的原因是
pow(arr[i],2)
2
视为一个浮点,使计算变得更加复杂,但是我也尝试使用arr[i]**2代替pow(arr[i],2),两种解决方案的性能几乎相等。一般来说,即使在没有数学变换的情况下对numpy数组进行简单迭代,numba编译函数的运行速度也比cython快。非常感谢您的见解!通过您的优化,我的cython函数的运行速度几乎与numba一样快。IIt与问题并不完全相关,但缺少一点。在开始时,分配数组的不成功归零占用了总运行时间的30%以上,并且至少在Numba中没有被编译器优化。@ead这只是出于好奇的问题。但不久前,我在cython中遇到了与pow类似的问题。如果你不使用I硬编码指数n Numba和SVML存在它在256位向量上调用SVML的pow函数,结果约为150µs。Cython中是否有一个简单的替代方案而不使用icc?@max9111,我必须承认我从未尝试过它。我可能宁愿用C编写代码并用Cython包装功能,而不愿尝试访问“intristics”直接从Cython
@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)
numba+vectorization+empty     3µs
np.square                     4µs
numba+vectorization           7µs
numba missed vectorization   11µs
cython+mult                  14µs
cython+pow                  356µs