首页 文章

类型化MemoryView的Cython性能不佳

提问于
浏览
0

我正在尝试使用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 形状 (640, 480) 需要80毫秒 . 但是,我需要将此函数调用数千至数万次,因此我有兴趣加快速度 .

这是我的Cython实现:

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实现使用类型MemoryView以支持 image_component 的切片 . 这个Cython实现在我的机器上需要250毫秒进行100次迭代(与之前相同的条件: image_component(640, 480) 数组) .

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

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

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

from distutils.core import setup
from distutils.extension import Extension
from Cython.Build import cythonize
import numpy

extensions = [
    Extension('proto_mpeg_computation', ['proto_mpeg_computation.pyx'],
          include_dirs=[numpy.get_include()]
          ),
]

setup(
   name = "proto_mpeg_x",
   ext_modules = cythonize(extensions)
)

1 回答

  • 1

    性能明显较差的原因是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乘16的数组复制到已经为 x 分配的内存中 . 与原始Python版本相比,它还没有更多的工作要做 . 再次,通过检查函数返回值 owndata 来确认 . 你会发现它是 True .

    在您的情况下,您应该考虑是否需要数据视图或数据副本 .


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

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

相关问题