Cython:释放内存的内存视图

Cython: Memory view of freed memory

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

我的问题是:释放指针总是安全的吗?内存视图对象是否以某种方式通知内存被释放,或者我应该以某种方式手动删除视图?另外,内存是否保证被释放,即使它被内存视图引用?

需要深入研究 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 后访问内存视图 不安全。你似乎侥幸逃脱的事实是幸运的。内存视图保持存在是安全的,前提是您不访问它。函数如:

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 的生命周期很明显,那么您可以像之前一样在 ptr 上调用 PyMem_Free