gpt4 book ai didi

python - 令人困惑的引用所有权 : how to properly deallocate (via Py_DECREF) objects of an object?

转载 作者:太空宇宙 更新时间:2023-11-04 03:29:21 24 4
gpt4 key购买 nike

我正在分析以下代码,它可以正确编译和运行,但会产生内存泄漏。

cfiboheap 是 Fibonacci 堆的 C 实现,以下代码是 cfiboheap 的 Cython 包装器(其中的一部分)。

我的疑虑是从插入函数开始的。对象 data 已在某处创建并传递给函数 insert()。由于该函数想要将此对象添加到 fiboheap,因此它增加了它的引用计数。但之后呢?所有权归谁所有?在我的理解中,C 函数 fh_insertkey() 只是借用了所有权。然后返回一个需要封装的专有指针,再由insert()返回。凉爽的。但是我的对象 data 及其引用计数?通过创建胶囊,我创建了一个新对象,但我并没有减少 data 的引用计数。这会产生内存泄漏。

(请注意,在 insert() 返回之前注释掉 Py_INCREF 或添加 Py_DECREF 会导致段错误。)

我的问题是:

1) 为什么在 insert() 期间需要增加 data 的引用计数?

2) 为什么在 extract() 期间不需要使用 Py_DECREF

3) 更一般地说,在 C 和 Python 之间跳转时如何准确跟踪引用所有权?

4) 如何正确地释放像 FiboHeap 这样的对象?我是否应该在 __dealloc__() 中预防性地使用 Py_XDECREF,如果是,如何使用?

谢谢!

cimport cfiboheap
from cpython.pycapsule cimport PyCapsule_New, PyCapsule_GetPointer
from python_ref cimport Py_INCREF, Py_DECREF

cdef inline object convert_fibheap_el_to_pycapsule(cfiboheap.fibheap_el* element):
return PyCapsule_New(element, NULL, NULL)

cdef class FiboHeap:

def __cinit__(FiboHeap self):
self.treeptr = cfiboheap.fh_makekeyheap()
if self.treeptr is NULL:
raise MemoryError()

def __dealloc__(FiboHeap self):
if self.treeptr is not NULL:
cfiboheap.fh_deleteheap(self.treeptr)

cpdef object insert(FiboHeap self, double key, object data=None):
Py_INCREF(data)
cdef cfiboheap.fibheap_el* retValue = cfiboheap.fh_insertkey(self.treeptr, key, <void*>data)
if retValue is NULL:
raise MemoryError()

return convert_fibheap_el_to_pycapsule(retValue)

cpdef object extract(FiboHeap self):
cdef void* ret = cfiboheap.fh_extractmin(self.treeptr)
if ret is NULL:
raise IndexError("FiboHeap is empty")

return <object> ret

cpdef object decrease_key(FiboHeap self, object element, double newKey):
cdef void* ret = cfiboheap.fh_replacekey(self.treeptr, convert_pycapsule_to_fibheap_el(element), newKey)
if ret is NULL:
raise IndexError("New Key is Bigger")

return <object> ret

请注意,这不是我写的,但我正在使用这个示例来更好地理解 obj 引用并阻止泄漏(因为我实际上正在使用代码)。

使用 FiboHeap 的主要代码(以及发生泄漏的地方)如下所示:

cdef dijkstra(Graph G, int start_idx, int end_idx):

cdef np.ndarray[object, ndim=1] fiboheap_nodes = np.empty([G.num_nodes], dtype=object) # holds all of our FiboHeap Nodes Pointers
Q = FiboHeap()
fiboheap_nodes[start_idx] = Q.insert(0, start_idx)
# Then occasionally:
Q.insert(...)
Q.decrease_key(...)
Q.extract()

return

extract 不是 peek,而是适当的 pop,因此它删除了 C fiboheap 中的 C 元素。

结论:data 的引用计数显然会导致内存泄漏,但为什么呢?以及如何阻止它?

最佳答案

1) 需要增加insert中的引用计数,因为它的引用计数会在插入结束时自动减少。 Cython 不知道您正在存储该对象供以后使用。 (您可以检查生成的 C 代码以查看函数末尾的 DECREF)。如果使用引用计数为 1 的对象(即 .insert(SomeObject()))调用 insert,则该对象将在没有 的插入结束时被销毁>增加

2) 如果对象在 extract 期间从 cfiboheap 中移除,那么你应该做一个 DECREF 来确认你不再拿住。首先将它转换为对象(这样你仍然持有对它的引用)

   cdef void* ret = cfiboheap.fh_extractmin(self.treeptr) # refcount is 1 here (from the INCREF when it was stored)
if ret==NULL:
# ...

ret_obj = <object>ret
# reference count should be 2 here - one for being on the heap and one for ret_obj. Casting to object increases the refcount in Cython
Py_DECREF(ret_obj) # 1 here
return ret_obj

3) 老实说,如果可以避免,请尽量不要使用 PyObject*!让 Cython 来完成工作要好得多。如果无法避免,则只需确保在存储对象时调用一次 INCREF,在停止存储对象时调用一次 DECREF

4) 您确实需要在 __dealloc__ 中减少堆上剩余的对象。一个非常简单的方法可能是所有 extract 直到 cfiboheap 为空:

try:
while True:
self.extract()
except IndexError:
pass # ignore the error - we're done emptying the heap

关于胶囊使用的评论:谁拥有它们指向的 fibheap_el(以及何时销毁)?如果它在 cfiboheap 被破坏时被破坏,那么你会遇到一个带有无效指针的胶囊仍然存在的问题。在某处使用这个胶囊可能会导致问题。如果它没有被 cfiboheap 破坏,那么您可能有另一个内存泄漏。

关于python - 令人困惑的引用所有权 : how to properly deallocate (via Py_DECREF) objects of an object?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38285729/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com