2016-07-09 19 views
1

コンパイルして正しく実行するが、メモリリークを生成する以下のコードを解析していました。参照所有権の混乱:オブジェクトのオブジェクトを(Py_DECREF経由で)適切に割り当て解除する方法はありますか?

cfiboheapはフィボナッチヒープのC実装であり、次のコードはcfiboheapのCythonラッパー(一部)です。

私の疑義は挿入機能から始まります。オブジェクトdataはどこかで作成され、関数insert()に渡されました。関数はこのオブジェクトをファイバーヒープに追加したいので、参照カウントを増加させます。しかしその後?所有権は誰に行くのですか?私の理解では、C関数fh_insertkey()は所有権を借りるだけです。次に、カプセル化が必要なプロプライエタリポインタを返し、によって返します。クール。しかし、私のオブジェクトdataとそのrefカウント?カプセルを作成することで新しいオブジェクトを作成していますが、参照カウントをdataに減らすことはありません。これにより、メモリリークが発生します。

Py_INCREFをコメントアウトするか、セグメンテーションフォールトでinsert()結果の返却前にPy_DECREFを追加することに注意してください。)

私の質問は以下のとおりです。

1)なぜそれがdataの参照カウントをインクリメントする必要がありますinsert()の間に?

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が、適切なポップではない、それはCで要素を削除されるようにC fiboheap。

結論:refカウントがdataであることはメモリリークを引き起こすことは明らかですが、なぜですか?それをやめる方法は?

+0

このメモリリークについての最初の(しかし異なる)質問が見つかりました[ここ](http://stackoverflow.com/questions/38251216/how-to-deallocate-a-typed-numpy-array-is-setting -callback-free-data-a-viable-op)を使用します。 – Gioker

+0

なぜあなたはカプセルをまったく作っていますか?それは役に立たず、安全ではないようです。また、「抽出する」は、ピークまたはポップを行いますか? – user2357112

+0

あなたが最初の質問でそれを言及して以来、私はここに投稿しています - 私は 'fiboheap'が本当にこれに答えるのに十分なことを理解しているとは思いません。 Cythonで 'PyObject *'と参照カウントを使用しようとするのを避けることができます。 @ user2357112が 'extract 'とは何かについて質問したところ、ここにはキーがあります.... – DavidW

答えて

1

1)参照カウントを挿入の最後に自動的に減らすため、参照カウントをinsertに増やす必要があります。 Cythonは後でオブジェクトを格納していることを知らない。 (生成されたCコードを検査して、機能の最後にDECREFを表示することができます)。参照カウント1のオブジェクト(すなわち、参照カウント1)でinsertが呼び出された場合。.insert(SomeObject())は、そのオブジェクトはINCREF

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__にデクリメントする必要があります。それはcfiboheapまで、すべてのextractである可能性があります行うには非常に簡単な方法が空である:カプセルの使用に関する

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

コメント:彼らはを指していることをfibheap_elを所有している(これは取得しません破壊された)? cfiboheapが破壊されたときに破壊されると、無効なポインタがまだ生きているカプセルの問題が発生します。このカプセルをどこかで使用すると、問題が発生する可能性があります。 cfiboheapによって破壊されない場合は、別のメモリリークが発生する可能性があります。

+0

ポイント1への回答はまだ少し不明です。 C関数 'insert'は要素の参照を借用するだけなので、なぜCythonが参照カウントを減らすべきですか?代わりに、問題は[ここ](https://docs.python.org/3/extending/extending.html#thin-ice)の説明に関連していると思います。つまり、ヒープの有効な要素へのポインタが後続の抽出中に処理されることがあり、同じ位置の後続の挿入を無効にすることが可能である。この意味では、Cythonが後で要素を格納していることを知らないと言ったときは、あなたは正しいです。 – Gioker

関連する問題