Cython内存视图比预期慢

4
我开始在Cython中使用memoryviews来访问NumPy数组。它们有很多优点,其中一个是比旧的NumPy缓冲区支持要快得多: http://docs.cython.org/src/userguide/memoryviews.html#comparison-to-the-old-buffer-support 然而,在我的一个例子中,旧的NumPy缓冲区支持比memoryviews更快!这怎么可能?我想知道我是否正确地使用了memoryviews?
这是我的测试:
import numpy as np
cimport numpy as np
cimport cython

@cython.boundscheck(False)
@cython.wraparound(False)
cpdef np.ndarray[np.uint8_t, ndim=2] image_box1(np.ndarray[np.uint8_t, ndim=2] im, 
                                               np.ndarray[np.float64_t, ndim=1] pd,  
                                               int box_half_size):
    cdef unsigned int p0 = <int>(pd[0] + 0.5)  
    cdef unsigned int p1 = <int>(pd[1] + 0.5)    
    cdef unsigned int top = p1 - box_half_size
    cdef unsigned int left = p0 - box_half_size
    cdef unsigned int bottom = p1 + box_half_size
    cdef unsigned int right = p0 + box_half_size    
    cdef np.ndarray[np.uint8_t, ndim=2] box = im[top:bottom, left:right] 
    return box 

@cython.boundscheck(False)
@cython.wraparound(False)
cpdef np.uint8_t[:, ::1] image_box2(np.uint8_t[:, ::1] im, 
                                    np.float64_t[:] pd,  
                                    int box_half_size):

    cdef unsigned int p0 = <int>(pd[0] + 0.5)  
    cdef unsigned int p1 = <int>(pd[1] + 0.5)    
    cdef unsigned int top = p1 - box_half_size
    cdef unsigned int left = p0 - box_half_size
    cdef unsigned int bottom = p1 + box_half_size
    cdef unsigned int right = p0 + box_half_size     
    cdef np.uint8_t[:, ::1] box = im[top:bottom, left:right]   
    return box 

时间测试结果如下:

image_box1:使用numpy: 100000次循环,3次最佳结果:每次循环耗时11.2微秒

image_box2:使用memoryview: 100000次循环,3次最佳结果:每次循环耗时18.1微秒

这些测量是在IPython中使用%timeit image_box1(im, pd, box_half_size)进行的。


1
我猜你是从Python计时这些函数的吧?因此第二个函数的返回值也是一个np.ndarray(我猜),这可能已经解释了减速的原因,因为制作np.ndarray需要一些额外的工作,在这里整体上没有做太多事情。 - seberg
是的,我使用IPython命令计时: %timeit image_box1(im,pd,box_half_size)我刚刚编辑了我的问题,包括来自cython的计时。 memoryviews仍然较慢! - martinako
更正!你是对的,延迟在于从numpy数组转换为memoryview的过程中! - martinako
1个回答

2

好的!我找到了问题。正如seberg所指出的那样,由于测量包括从numpy数组自动转换为memoryview,因此memoryviews看起来比较慢。

我使用以下函数在cython模块内部测量时间:

def test(params):   
    import timeit
    im = params[0]
    pd = params[1]
    box_half_size = params[2]
    t1 = timeit.Timer(lambda: image_box1(im, pd, box_half_size))
    print 'image_box1: typed numpy:'
    print min(t1.repeat(3, 10))
    cdef np.uint8_t[:, ::1] im2 = im
    cdef np.float64_t[:] pd2 = pd
    t2 = timeit.Timer(lambda: image_box2(im2, pd2, box_half_size))
    print 'image_box2: memoryview:'
    print min(t2.repeat(3, 10)) 

结果:

图像框1:已输入的numpy: 9.07607864065e-05

图像框2:memoryview: 5.81799904467e-05

因此,memoryviews确实更快!

请注意,在调用image_box2之前,我将im和pd转换为memoryviews。如果我不执行此步骤并直接传递im和pd,则image_box2会变慢:

图像框1:已输入的numpy: 9.12262257771e-05

图像框2:memoryview: 0.000185245087778


我不确定这是否完全公平,因为如果您输入,则可能也应在Cython中键入数组。但无论如何,这都归结为一些转换(数组-> memoryview或memoryview->数组),但这只是微小的开销。 - seberg
根据您的建议,我尝试通过将im和pd复制到相应的numpy缓冲类型中来使测试更加公平。我在测试函数中执行此操作,然后将缓冲区类型传递给image_box1。但是,如果我尝试将这些缓冲区类型传递给image_box1,则会出现编译错误“仅允许将缓冲区类型作为函数本地变量”。我一直在试图找到解决此错误的方法,但似乎找不到任何方法。我开始认为这是Cython的限制。 - martinako

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接