Cython:释放内存的内存视图

时间:2016-03-28 15:05:43

标签: python memory cython free memoryview

在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](但它不会返回原始数据)。

我的问题是:解除指针是否总是安全的?内存视图对象是否以某种方式通知了正在释放的内存,或者我应该以某种方式手动删除视图?此外,即使存储器视图引用内存,也可以保证释放内存吗?

1 个答案:

答案 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