Cython:将C缓冲区memoryview返回给Python

时间:2018-07-12 17:16:03

标签: python python-3.x memory buffer cython

我有以下定义了一个C缓冲区(c_buffer)的Cython代码:

ctypedef struct my_struct_t:
    float x
    float y

cdef class CMyClass:
    cdef my_struct_t c_buffer[1000]

    def get_array(self):
        return <my_struct_t[:1000]>&self.c_buffer[0]

    def get_memoryview(self):
        return memoryview(<my_struct_t[:1000]>&self.c_buffer[0])

我正在使用此类存储最终要进入OpenGL VBO缓冲区的元素。我想做的是避免不必要的内存副本。

致电get_array()时得到类型

的结果
<c_wrappers.array object at 0x7fffce17d650>

使用get_memoryview()的结果是:

<memory at 0x7fffd242e648>
  1. 它们之间有什么区别(功能/速度)?我正在阅读有关Typed Memoryviews的正式文档,但主要关注numpy。我在这里正确返回内存视图吗?

  2. 现在缓冲区已固定(最多1000个元素)。在Cython中是否存在我可以使用的动态数组,并且它可以自动为我处理内存(在运行时添加/删除元素)并具有连续的内存布局(最终可以提供给OpenGL VBO)?还是应该使用from libcpp.vector cimport vector

1 个答案:

答案 0 :(得分:1)

这是一个非常复杂的问题!有一些方面需要考虑。

速度:

让我们从一个简单的int缓冲区开始(我跳过了不必要的&c_buffer[0]-business):

%%cython
cdef class CMyClass:
    cdef int c_buffer[1000]

    def get_array(self):
        return <int[:1000]>self.c_buffer

    def get_memoryview(self):
        return memoryview(<int[:1000]>self.c_buffer)

“键入的内存视图”在Cython中有些不透明,有些类非常相似,并且根据函数的签名从函数返回:

但是,以上所有都不是您在第二个函数中返回的内存视图:它返回Python's memoryview

非常令人困惑!就个人而言,我保持简单,并相信Cython返回最合适的类-对我来说,它只是一个缓冲区。

当我们测量速度时,第一个版本会更快,因为将array_obj包装到Python的memoryview中只会增加复杂性:

>>>c=CMyClass()
>>>%timeit c.get_array()
377 ns ± 1.69 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
>>>%timeit c.get_memoryview()
561 ns ± 2.31 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

寿命:

c_buffer中的内存未复制:

>>>c=CMyClass()
>>>c.get_array()[0]=42
>>>print(c.get_memoryview()[0])

听起来像是一件好事,但事实并非如此!问题:c_buffer不是Python对象,当它超出范围时,内存视图的数据指针就会晃来晃去:

>>c=CMyClass()
>>>c.get_array()[0]=42
>>>c=c.get_array()   # old c-object is now destroyed
>>>print(c[0])       # anything can happen!
-304120624

我很幸运,python并没有崩溃,但是可能会崩溃,因为在将c绑定到memoryview之后,底层对象被销毁并释放了内存。

使用std::vector不会对您有帮助。您需要的是一个带有引用计数的真实Python对象!例如,我们可以使用Cython的数组:

%%cython 

from cython.view cimport array as cvarray
cdef class CMyClass:
    cdef int[:] c_buffer

    def __cinit__(self):
        self.c_buffer = cvarray(shape=(1000,), itemsize=sizeof(int), format="i")

    def  get_array(self):
        cdef int[:] res=self.c_buffer # nobody needs to know which class we use
        return res

现在上面的代码很安全:

>>c=CMyClass()
>>>c.get_array()[0]=42
>>>c=c.get_array()   # old c-object is now destroyed
>>>print(c[0])       # but the underlying memory is still alive
42

自定义结构:

但是,如上例所示,海关结构如何?可能最简单的方法是使用numpy:

%%cython -a
import numpy as np
cimport numpy as np

#define a type for memory view
ctypedef packed struct my_struct_t:
    np.float32_t x
    np.float32_t y

#define a type for numpy-array (is a python-object)
my_struct = np.dtype([
    ('x', np.float32, 1), 
    ('y', np.float32, 1),  
])

cdef class CMyClass:
    cdef object c_buffer

    def __cinit__(self):
        self.c_buffer = np.empty(1000,dtype=my_struct)

    def  get_array(self):
        cdef my_struct_t[:] res=self.c_buffer
        return res

广告宣传的作品:

>>>c=CMyClass()
>>>c.get_array()[0]={'x':42,'y':42}
>>>c=c.get_array()   # old c-object is now destroyed
>>>print(c[0])       # but this is still ok
{'x': 42.0, 'y': 42.0}

再说两次:

  • 使用numpy的速度较慢-get_array()的速度比原始get_array()版本的速度慢三倍

  • 使用my_struct_t c_buffer并不会真正帮助您(除了危险),因为没有规则将数据从c-struct转换为python对象,但是此检查在运行时发生时间,当访问数组的元素时。