代码之家  ›  专栏  ›  技术社区  ›  Andrej Kesely

cython:将c缓冲区内存视图返回给python

  •  1
  • Andrej Kesely  · 技术社区  · 6 年前

    我有以下cython代码,其中定义了一个c缓冲区( c_buffer )以下内容:

    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 回复  |  直到 6 年前
        1
  •  1
  •   ead    6 年前

    那是一个相当复杂的问题!有一些方面需要考虑。

    速度:

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

    %%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中有些不透明,有些类非常相似,根据函数的签名从函数返回:

    但是,上面这些都不是您在第二个函数中返回的memory视图:它返回 Python's memoryview 是的。

    相当混乱!就我个人而言,我保持简单,相信cython会返回最合适的类——对我来说,它只是一个缓冲。

    当我们测量速度时,第一个版本会更快,因为将ARYAYOBO封装到Python的内存视图中只会增加复杂性:

    >>>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 U缓冲器 不是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() 比原来慢三倍 获取数组() 版本

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