Cython:将 C 缓冲区内存视图返回到 Python
Cython: Returning C buffer memoryview to Python
我有以下 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])
我正在使用这个 class 来存储最终进入 OpenGL VBO 缓冲区的元素。我想做的是避免不必要的内存副本。
当我调用 get_array()
时,我得到类型为
的结果
<c_wrappers.array object at 0x7fffce17d650>
和get_memoryview()
的结果是:
<memory at 0x7fffd242e648>
它们有什么区别(在functionality/speed中)?我正在阅读关于 Typed Memoryviews 的官方文档,但它主要关注 numpy。我在这里返回的内存视图是否正确?
现在缓冲区是固定的(最多 1000 个元素)。 Cython 中是否存在我可以使用的动态数组,它会自动为我处理内存(在运行时为 adding/removing 元素)并具有连续的内存布局(我最终可以提供给 OpenGL VBO)?或者我应该使用 from libcpp.vector cimport vector
?
这是一个相当复杂的问题!有些方面需要考虑。
速度:
让我们从一个简单的 int
-buffer 开始(我跳过了不必要的 &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)
"Typed memory view" 在 Cython 中有些不透明,有一些 classes 非常相似并且根据函数的签名从函数中 returned:
- array
- memoryview
- memoryviewslice
- 还有一个辅助结构 __Pyx_memviewslice,它不是 return 在函数中使用的。
但是,上面的 none 是您在第二个函数中 return 的内存视图:它 returns Python's memoryview.
相当混乱!就个人而言,我保持简单并相信 Cython return 最合适 class - 对我来说它只是一个缓冲区。
当我们测量速度时,第一个版本会更快,因为将 array_obj 包装到 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_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
绑定到内存视图后,基础对象被销毁并释放内存。
使用 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对象,但此检查发生在 运行 时间,即访问数组元素时。
我有以下 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])
我正在使用这个 class 来存储最终进入 OpenGL VBO 缓冲区的元素。我想做的是避免不必要的内存副本。
当我调用 get_array()
时,我得到类型为
<c_wrappers.array object at 0x7fffce17d650>
和get_memoryview()
的结果是:
<memory at 0x7fffd242e648>
它们有什么区别(在functionality/speed中)?我正在阅读关于 Typed Memoryviews 的官方文档,但它主要关注 numpy。我在这里返回的内存视图是否正确?
现在缓冲区是固定的(最多 1000 个元素)。 Cython 中是否存在我可以使用的动态数组,它会自动为我处理内存(在运行时为 adding/removing 元素)并具有连续的内存布局(我最终可以提供给 OpenGL VBO)?或者我应该使用
from libcpp.vector cimport vector
?
这是一个相当复杂的问题!有些方面需要考虑。
速度:
让我们从一个简单的 int
-buffer 开始(我跳过了不必要的 &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)
"Typed memory view" 在 Cython 中有些不透明,有一些 classes 非常相似并且根据函数的签名从函数中 returned:
- array
- memoryview
- memoryviewslice
- 还有一个辅助结构 __Pyx_memviewslice,它不是 return 在函数中使用的。
但是,上面的 none 是您在第二个函数中 return 的内存视图:它 returns Python's memoryview.
相当混乱!就个人而言,我保持简单并相信 Cython return 最合适 class - 对我来说它只是一个缓冲区。
当我们测量速度时,第一个版本会更快,因为将 array_obj 包装到 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_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
绑定到内存视图后,基础对象被销毁并释放内存。
使用 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对象,但此检查发生在 运行 时间,即访问数组元素时。