2016-07-09 12 views
1

Ich analysierte den folgenden Code, der korrekt kompiliert und ausgeführt wird, aber ein Speicherleck generiert. Die cfiboheap ist eine C-Implementierung eines Fibonacci Heap und der folgende Code ist ein Cython Wrapper (ein Teil davon) für cfiboheap.Verwirrendes Referenzeigentum: wie man (über Py_DECREF) Objekte eines Gegenstandes richtig freigibt?

Meine Zweifel beginnt auf der Einfügefunktion. Das Objekt data wurde irgendwo angelegt und an die Funktion insert() übergeben. Da die Funktion dieses Objekt zum Fiboheap hinzufügen möchte, erhöht sie ihre Referenzzahl. Aber danach? An wen geht das Eigentum? In meinem Verständnis leiht die C-Funktion fh_insertkey() nur das Eigentum. Dann gibt es einen proprietären Zeiger zurück, der eingekapselt werden muss, und wird dann von insert() zurückgegeben. Cool. Aber mein Objekt data und seine Ref zählen? Durch das Erstellen der Kapsel erstelle ich ein neues Objekt, aber ich verringere nicht die Ref-Anzahl von data. Dies erzeugt das Speicherleck.

(Beachten Sie, dass Auskommentierung Py_INCREF oder das Hinzufügen von Py_DECREF vor der Rückkehr insert() Ergebnisse in einem Segmentierungsfehler.)

Meine Fragen sind:

1) Warum ist es notwendig, den Verweiszähler von data zu erhöhen während der insert()?

2) Warum ist es nicht notwendig, einen Py_DECREF während der extract() zu verwenden?

3) Wie kann ich den Referenzbesitz beim Springen zwischen C und Python genauer verfolgen?

4) Wie man ein Objekt wie dieses FiboHeap richtig freigibt? Sollte ich präventiv eine Py_XDECREF in __dealloc__() verwenden und wenn ja, wie?

Danke!

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 

Beachten Sie, dass diese von mir geschrieben wurde nicht, aber ich bin mit diesem Beispiel besser zu verstehen obj Referenzierung und das Leck zu stoppen (da ich eigentlich den Code verwende).

Der Hauptcode, der Verwendung von FiboHeap (und wo das Leck passiert) sieht wie folgt aus macht:

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 kein spähen, sondern eine richtige Pop, so dass er das C-Element in der löscht C Fibohap.

Fazit: Es scheint klar, dass die ref count von data einen Speicherverlust verursacht, aber warum? Und wie man es stoppt?

+0

Die erste (aber andere) Frage zu diesem Speicherleck finden Sie [hier] (http://stackoverflow.com/questions/38251216/how-to-deallocate-a-typed-numpy-array-is-setting -callback-free-Daten-a-lebensfähige-op). – Gioker

+0

Warum machen Sie überhaupt eine Kapsel? Es scheint nutzlos und unsicher. Macht 'extract' auch einen Peek oder Pop? – user2357112

+0

Ich poste hier, da du es in deiner ersten Frage erwähnt hast - ich glaube nicht, dass ich verstehe, was "fiboheap" gut genug macht, um wirklich darauf zu antworten. Es würde vermeiden, 'PyObject *' s und Referenzzählung in Cython zu verwenden - es ist sehr schwer, richtig zu machen. Die Frage @ user2357112 gefragt, was 'extract' tut ist hier der Schlüssel .... – DavidW

Antwort

1

1) Es ist notwendig, die Referenzzählung in insert zu erhöhen, da die Referenzzählung am Ende der Einfügung automatisch verringert wird. Cython weiß nicht, dass Sie das Objekt für später speichern. (Sie können den generierten C-Code überprüfen, um das DECREF am Ende der Funktion zu sehen). Wenn insert mit einem Objekt der Referenzzählung 1 aufgerufen wird (d. H..insert(SomeObject()), dann das Objekt wäre ohne die INCREF

2) am Ende des Einsatzes zerstört werden, wenn das Objekt aus den cfiboheap während extract entfernt wird, dann sollten Sie ein DECREF tun, um die Tatsache zu bestätigen, dass Sie es nicht mehr halten. Wirf ihn zum Objekt zuerst (so dass Sie noch einen Verweis auf sie halten)

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) Ehrlich Sie versuchen nicht PyObject* zu verwenden, wenn Sie es vermeiden können! Es ist viel besser, Cython die Arbeit machen zu lassen. Wenn Sie es nicht vermeiden können, dann müssen Sie nur sicherstellen, INCREF wird einmal aufgerufen, wenn Sie das Objekt speichern, und DECREF wird einmal aufgerufen, wenn Sie die Speicherung stoppen.

4) Sie müssen die verbleibenden Objekte auf dem Heap in __dealloc__ dekretieren. Eine sehr einfache Art und Weise zu tun, um alle möglicherweise extract bis zum cfiboheap leer ist:

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

Ein Kommentar über die Verwendung von Kapseln: Wer die fibheap_el besitzt, die sie verweisen auf (und wann wird diese erhalten zerstört)? Wenn es zerstört wird, wenn die cfiboheap zerstört wird, dann haben Sie das Problem einer Kapsel mit einem ungültigen Zeiger noch am Leben. Die Verwendung dieser Kapsel könnte zu Problemen führen. Wenn es nicht durch die cfiboheap zerstört wird, haben Sie möglicherweise ein anderes Speicherleck.

+0

Antwort zu Punkt 1) ist noch ein wenig unklar. Die C-Funktion 'insert' sollte nur die Referenz des Elements ausleihen, also warum sollte Cython die ref count verringern? Stattdessen denke ich, dass das Problem damit zusammenhängt, was [hier] erklärt wurde (https://docs.python.org/3/extending/extending.html#thinice), das ist der Zeiger auf ein gültiges Element des Heap-Feldes während eines nachfolgenden Extraktes entsorgt werden, möglicherweise ein nachfolgender Einsatz an der gleichen Stelle ungültig machen. In diesem Sinne hast du recht, wenn du sagst, dass Cython nicht weiß, dass ich das Element für später speichere. – Gioker