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
。
在 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
。