类型化MemoryView的Cython性能不佳

时间:2017-04-21 01:08:07

标签: python numpy cython

我正在尝试使用Cython加速一些纯Python代码。这是原始的Python代码:

import numpy as np
def image_to_mblocks(image_component):
    img_shape = np.shape(image_component)
    v_mblocks = img_shape[0] // 16
    h_mblocks = img_shape[1] // 16
    x = image_component
    x = [x[i * 16:(i + 1) * 16:, j * 16:(j + 1) * 16:] for i in range(v_mblocks) for j in range(h_mblocks)]
    return x

参数image_component是一个二维numpy.ndarray,其中每个维度的长度可以被16整除。在纯Python中,这个函数很快 - 在我的机器上,100个调用形状image_component的{​​{1}}需要80毫秒。但是,我需要将这个函数调用数千到数万次,所以我有兴趣加快它的速度。

这是我的Cython实现:

(640, 480)

Cython实现使用类型化的MemoryView来支持import numpy as np cimport numpy as np cimport cython ctypedef unsigned char DTYPE_pixel cpdef np.ndarray[DTYPE_pixel, ndim=3] image_to_mblocks(unsigned char[:, :] image_component): cdef int i cdef int j cdef int k = 0 cdef int v_mblocks = image_component.shape[0] / 16 cdef int h_mblocks = image_component.shape[1] / 16 cdef np.ndarray[DTYPE_pixel, ndim=3] x = np.empty((v_mblocks*h_mblocks, 16, 16), dtype=np.uint8) for j in range(h_mblocks): for i in range(v_mblocks): x[k] = image_component[i * 16:(i + 1) * 16:, j * 16:(j + 1) * 16:] k += 1 return x 的切片。这个Cython实现在我的机器上需要250毫秒进行100次迭代(与以前相同的条件:image_componentimage_component数组)。

这是我的问题:在我给出的示例中,为什么Cython无法胜过纯Python实现?

我相信我已经遵循Cython documentation for working with numpy arrays中的所有步骤,但我未能达到我期待的性能提升。

供参考,这是我的setup.py文件的样子:

(640, 480)

1 个答案:

答案 0 :(得分:2)

性能明显较差的原因是Cython版本正在复制数据,而原始版本正在创建对现有数据的引用。

该行

x[i * 16:(i + 1) * 16:, j * 16:(j + 1) * 16:]

在原始x数组上创建一个视图(即如果您更改x,则视图也会更改)。您可以通过检查从Python函数返回的数组元素的numpy owndata标志是False来确认这一点。这个操作非常便宜,因为它只是存储一个指针和一些形状/步幅信息。

在你做的Cython版本中

x[k] = image_component[i * 16:(i + 1) * 16:, j * 16:(j + 1) * 16:]

这需要将16 x 16阵列复制到已为x分配的内存中。它不是超慢,但还有比原始Python版本更多的工作要做。再次,通过检查函数返回值的owndata来确认。你应该发现它是True

在您的情况下,您应该考虑是否需要查看数据或数据副本。

这不是Cython在我看来会有很多帮助的问题。 Cython有一些很好的加速索引单个元素,但是当你开始索引切片时,它的行为与基础Python / numpy相同(这对于这种类型的使用实际上非常有效)。

我怀疑您将原始Python代码放入Cython并将image_component键入unsigned char[:, :]np.ndarray[DTYPE_pixel, ndim=2]会获得一点点收益。您还可以通过不使用x并直接返回列表推导来减少一小部分引用计数。除此之外,我不知道你将如何获得更多。