Numpy数组的乘法行为与纯Python和Cython不同



在纯Python代码中:

案例A:

retimg = np.zeros((dstH, dstW, 3), dtype=np.uint8)
A = img[x % (scrH - 1), y % (scrW - 1)]
B = img[x % (scrH - 1), y1 % (scrW - 1)]
C = img[x1 % (scrH - 1), y % (scrW - 1)]
D = img[x1 % (scrH - 1), y1 % (scrW - 1)]
retimg[i, j] = A * (1 - mu) * (1 - nu) + B * mu * (1 - nu) + C * (1 - mu) * nu + D * mu * nu

案例B:

retimg = np.zeros((dstH, dstW, 3), dtype=np.uint8)
A = img[x % (scrH - 1), y % (scrW - 1)]
B = img[x % (scrH - 1), y1 % (scrW - 1)]
C = img[x1 % (scrH - 1), y % (scrW - 1)]
D = img[x1 % (scrH - 1), y1 % (scrW - 1)]
(r, g, b) = (
A[0] * (1 - mu) * (1 - nu) + B[0] * mu * (1 - nu) + C[0] * (1 - mu) * nu + D[0] * mu * nu,
A[1] * (1 - mu) * (1 - nu) + B[1] * mu * (1 - nu) + C[1] * (1 - mu) * nu + D[1] * mu * nu,
A[2] * (1 - mu) * (1 - nu) + B[2] * mu * (1 - nu) + C[2] * (1 - mu) * nu + D[2] * mu * nu)
retimg[i, j] = (r, g, b)

Case ACase B快得多

然后我使用Cython来加快执行速度。

案例C:

cdef np.ndarray[DTYPEU8_t, ndim=3] dst = np.zeros((dstH, dstW, 3), dtype=np.uint8)
cdef np.ndarray[DTYPEU8_t, ndim=1] A,B,C,D
A = img[x % (scrH - 1), y % (scrW - 1)]
B = img[x % (scrH - 1), y1 % (scrW - 1)]
C = img[x1 % (scrH - 1), y % (scrW - 1)]
D = img[x1 % (scrH - 1), y1 % (scrW - 1)]
retimg[i, j] = A * (1 - mu) * (1 - nu) + B * mu * (1 - nu) + C * (1 - mu) * nu + D * mu * nu

案例D:

cdef np.ndarray[DTYPEU8_t, ndim=3] dst = np.zeros((dstH, dstW, 3), dtype=np.uint8)
cdef float r,g,b
cdef np.ndarray[DTYPEU8_t, ndim=1] A,B,C,D
A = img[x % (scrH - 1), y % (scrW - 1)]
B = img[x % (scrH - 1), y1 % (scrW - 1)]
C = img[x1 % (scrH - 1), y % (scrW - 1)]
D = img[x1 % (scrH - 1), y1 % (scrW - 1)]
(r, g, b) = (
A[0] * (1 - mu) * (1 - nu) + B[0] * mu * (1 - nu) + C[0] * (1 - mu) * nu + D[0] * mu * nu,
A[1] * (1 - mu) * (1 - nu) + B[1] * mu * (1 - nu) + C[1] * (1 - mu) * nu + D[1] * mu * nu,
A[2] * (1 - mu) * (1 - nu) + B[2] * mu * (1 - nu) + C[2] * (1 - mu) * nu + D[2] * mu * nu)
retimg[i, j] = (r, g, b)

Case CCase D慢得多

为什么Numpy乘法数组的行为与Python和Cython不同?理论上Case C应该比Case D快。

这里Case CCase D慢的原因是由于临时变量的类型。实际上,在Case C中,许多临时数组是隐式创建和删除的。这会导致大量内存分配。相对于CPython解释器,内存分配速度相当快。然而,当使用Cython优化代码时,分配速度慢得令人望而却步,因为它们比飞行乘法慢得多。此外,使用Cython,标量表达式可以进行优化,因此它们使用处理器寄存器,而基于数组的表达式通常不会进行优化,而是使用慢速内存层次结构(因为这很难做到(。更不用说Numpy调用可能会增加额外的显著开销。

在我的机器上,1个分配/解除分配的成本比计算完整表达式花费更多的时间。

避免分配的一个解决方案是向Numpy指定数组的目的地,并尽可能避免基于数组的临时操作。下面是一个未经测试的例子:

# tmp is a predefined temporary array and res the resulting array
np.multiply(A, (1 - mu) * (1 - nu), out=res)
np.multiply(B, mu * (1 - nu), out=tmp)
np.add(tmp, res, out=res)
np.multiply(C, (1 - mu) * nu, out=tmp)
np.add(tmp, res, out=res)
np.multiply(D, mu * nu, out=tmp)
np.add(tmp, res, out=res)

请注意,上述解决方案并不能解决问题(与寄存器的使用和Numpy的开销有关(,而Case D应该解决这些问题。

在Cython中键入np.ndarray唯一能实现的就是更快地索引单个元素。数组切片、整个数组操作(如*+(和其他Numpy函数调用不会加速。

对于情况D,A[0]B[0]C[0]A[1]等被有效地索引并直接与C浮点相乘,因此该计算非常快速。相反,在C的情况下,您有一组数组乘法,这些乘法作为一个普通的Python函数调用进行。由于数组很小(3个元素长(,因此Python函数调用的成本很高。

retimg[i, j] = (r, g, b)最好写成:

retimg[i,j,0] = r
retimg[i,j,1] = g
retimg[i,j,2] = b

以利用索引(即Cython做得好的地方(。Cython可能会自然地优化它(但可能不会那么远(。


总之:除非您正在进行单元素索引,否则将事物键入为np.ndarray是毫无意义的。如果你不这样做,它实际上会浪费时间做不必要的类型检查。

相关内容

  • 没有找到相关文章

最新更新