在Cython代码中,我可以分配一些内存并将其包装在内存视图中,例如:像这样:
cdef double* ptr
cdef double[::1] view
ptr = <double*> PyMem_Malloc(N*sizeof('double'))
view = <double[:N]> ptr
如果我现在使用PyMem_Free(ptr)
释放内存,尝试访问ptr[i]
之类的元素会引发错误。但是,我可以安全地尝试访问view[i]
(但它不会返回原始数据)。
我的问题是:解除指针是否总是安全的?内存视图对象是否以某种方式通知了正在释放的内存,或者我应该以某种方式手动删除视图?此外,即使存储器视图引用内存,也可以保证释放内存吗?
答案 0 :(得分:6)
需要一点点C语言代码来证明这一点,但是:
第view = <double[:N]> ptr
行实际上生成了一个__pyx_array_obj
。这与the documentation as a "Cython array" and cimportable as cython.view.array
中详细介绍的类型相同。 Cython数组确实有一个称为callback_free_data
的可选成员,可以充当析构函数。
该行翻译为:
struct __pyx_array_obj *__pyx_t_1 = NULL;
# ...
__pyx_t_1 = __pyx_array_new(__pyx_t_2, sizeof(double), PyBytes_AS_STRING(__pyx_t_3), (char *) "c", (char *) __pyx_v_ptr);
({__pyx_t_2
和__pyx_t_3
只是临时存储大小和格式的临时文件)。如果我们查看__pyx_array_new
内部,我们首先会看到数组的data
成员被直接分配给以__pyx_v_ptr
传递的值
__pyx_v_result->data = __pyx_v_buf;
(即未复制 ),其次未设置callback_free_data
。 旁注: cython.view.array
的C代码实际上是从Cython code生成的,因此,如果要进一步研究,可能比生成的C更容易阅读。
从本质上讲,memoryview包含一个cython.view.array
,该指针具有指向原始数据的指针,但没有设置callback_free_data
。当memoryview死亡时,将调用cython.view.array
的析构函数。这样可以清理一些内部信息,但不会释放它指向的数据(因为它没有指示这样做的迹象)。
因此,在调用PyMem_Free
之后访问memoryview是不安全。您似乎无法摆脱的事实真是太幸运了。不过,如果您不访问它,可以安全地保留memoryview。像这样的功能:
def good():
cdef double* ptr
cdef double[::1] view
ptr = <double*> PyMem_Malloc(N*sizeof('double'))
try:
view = <double[:N]> ptr
# some other stuff
finally:
PyMem_Free(ptr)
# some other stuff not involving ptr or view
会没事的。像这样的功能:
def bad():
cdef double* ptr
cdef double[::1] view
ptr = <double*> PyMem_Malloc(N*sizeof('double'))
try:
view = <double[:N]> ptr
# some other stuff
finally:
PyMem_Free(ptr)
view[0] = 0
return view
这将是一个坏主意,因为它会传回不指向任何内容的内存视图,并在查看的数据被释放后访问view
。
您绝对应该确保在某个时候调用PyMem_Free
,否则会发生内存泄漏。如果view
被遗忘了,因此很难跟踪生命周期的一种方法是手动创建一个cython.view.array
集的callback_free_data
:
cdef view.array my_array = view.array((N,), allocate_buffer=False)
my_array.data = <char *> ptr
my_array.callback_free_data = PyMem_Free
view = my_array
如果view
的生命周期很明显,那么您可以像往常一样在PyMem_Free
上致电ptr
。