我正在分析以下代码,它们编译并正确运行,但会产生内存泄漏。
cfiboheap
是Fibonacci Heap的C实现,下面的代码是cfiboheap
的Cython包装器(它的一部分)。
我对插入功能的怀疑开始了。对象data
已在某处创建并传递给函数insert()
。由于函数想要将此对象添加到fiboheap,因此会增加其引用计数。但事后呢?所有权归谁所有?根据我的理解,C函数fh_insertkey()
只是借用了所有权。然后它返回一个需要封装的专有指针,然后由insert()
返回。凉。但我的对象data
及其引用计数?通过创建胶囊我正在创建一个新对象,但我没有减少data
的引用计数。这会产生内存泄漏。
(请注意,在Py_INCREF
返回之前注释Py_DECREF
或添加insert()
会导致细分错误。)
我的问题是:
1)为什么有必要在data
期间增加insert()
的引用次数?
2)为什么在Py_DECREF
期间没有必要使用extract()
?
3)更一般地说,在C和Python之间跳转时如何准确跟踪引用所有权?
4)如何正确释放像这个FiboHeap这样的对象?我应该预防性地使用Py_XDECREF
中的__dealloc__()
,如果是,请如何使用?
谢谢!
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
不是一个偷看,而是一个正确的弹出,所以它正在删除C fiboheap中的C元素。
总之:似乎很清楚data
的引用数会导致内存泄漏,但为什么呢?以及如何阻止它?
答案 0 :(得分:1)
1)有必要增加insert
中的引用计数,因为它的引用计数将在插入结束时自动减少。 Cython不知道您是否正在存储该对象以供日后使用。 (您可以检查生成的C代码以查看函数末尾的DECREF
)。如果使用引用计数1(即insert
)调用.insert(SomeObject())
,则在插入结束时对象将被销毁,而不会INCREF
2)如果在cfiboheap
期间将对象从extract
移除,那么您应该执行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
破坏,那么你可能会有另一个内存泄漏。