Looking for python Keywords? Try Ask4Keywords

Python LanguageMüllsammlung


Bemerkungen

Im Kern ist Pythons Garbage-Collector (ab 3.5) eine einfache Implementierung der Referenzzählung. Bei jeder Referenz auf ein Objekt (z. B. a = myobject ) wird der Referenzzähler für dieses Objekt (myobject) inkrementiert. Jedes Mal, wenn eine Referenz entfernt wird, wird der Referenzzähler dekrementiert, und sobald der Referenzzähler 0 erreicht, wissen wir, dass nichts einen Verweis auf dieses Objekt enthält, und wir können es aufheben.

Ein häufiges Missverständnis über die Funktionsweise der Python-Speicherverwaltung besteht darin, dass das Schlüsselwort del Objektspeicher freigibt. Das ist nicht wahr. Was tatsächlich passiert, ist, dass das Schlüsselwort del lediglich die refcount der Objekte dekrementiert, was bedeutet, dass, wenn Sie es so oft aufrufen, dass die refcount-Zahl null ist, das Objekt möglicherweise als Garbage-Collection erfasst wird (selbst wenn tatsächlich noch Verweise auf das an anderer Stelle im Code verfügbare Objekt vorhanden sind ).

Python erstellt oder bereinigt Objekte aggressiv, wenn sie zum ersten Mal benötigt werden. Wenn ich die Zuweisung a = object () durchführe, wird der Speicher für das Objekt zu diesem Zeitpunkt zugewiesen (cpython verwendet manchmal bestimmte Objekttypen, z. B. Listen unter der Haube). Meistens ist jedoch kein freier Objektpool vorhanden, und die Zuordnung wird ausgeführt, wenn Sie sie benötigen. Sobald der refcount auf 0 dekrementiert wird, bereinigt der GC ihn.

Generationsmüllsammlung

In den 1960er Jahren entdeckte John McCarthy einen fatalen Fehler bei der erneuten Zählung der Müllsammlung, als er den von Lisp verwendeten Refcounting-Algorithmus implementierte: Was passiert, wenn sich zwei Objekte in einer zyklischen Referenz auf einander beziehen? Wie können Sie diese beiden Objekte müll sammeln, auch wenn es keine externen Verweise darauf gibt, wenn sie sich immer auf einander beziehen? Dieses Problem erstreckt sich auch auf jede zyklische Datenstruktur, beispielsweise auf Ringpuffer oder zwei aufeinanderfolgende Einträge in einer doppelt verknüpften Liste. Python versucht, dieses Problem zu beheben, indem er einen etwas anderen Garbage Collection-Algorithmus namens Generational Garbage Collection verwendet .

Wenn Sie also ein Objekt in Python erstellen, wird es am Ende einer doppelt verknüpften Liste hinzugefügt. Gelegentlich durchläuft Python diese Liste, prüft, auf welche Objekte sich auch die Objekte in der Liste beziehen, und wenn sie sich auch in der Liste befinden (wir werden sehen, warum sie sich nicht in einem Moment befinden), verringern sie ihre Refcounts weiter. An diesem Punkt (tatsächlich gibt es einige Heuristiken, die bestimmen, wann Dinge verschoben werden, aber nehmen wir an, dass es nach einer einzelnen Sammlung geht, um die Dinge einfach zu halten), wenn immer noch ein refcount von mehr als 0 vorhanden ist, wird in eine andere verknüpfte Liste mit dem Namen "Generation 1" befördert. (Deshalb sind nicht immer alle Objekte in der Generierungsliste 0), auf die diese Schleife seltener angewendet wird. Hier kommt die Generationsspeicherbereinigung ins Spiel. Standardmäßig gibt es in Python 3 Generationen (drei verknüpfte Objektlisten): Die erste Liste (Generation 0) enthält alle neuen Objekte. Wenn ein GC-Zyklus auftritt und die Objekte nicht gesammelt werden, werden sie in die zweite Liste (Generation 1) verschoben. Wenn ein GC-Zyklus in der zweiten Liste ausgeführt wird und sie immer noch nicht gesammelt werden, werden sie in die dritte Liste (Generation 2) verschoben ). Die dritte Generation-Liste ("Generation 2" genannt, da wir keine Indexierung durchführen) besteht aus weniger gesammelten Abfällen als den ersten beiden. Die Idee ist, dass, wenn Ihr Objekt langlebig ist, es nicht so wahrscheinlich ist, dass es gaschiert wird und möglicherweise niemals Sie sollten während der gesamten Lebensdauer Ihrer Anwendung eine GC-Analyse durchführen, sodass es nicht unnötig ist, Zeit für die Überprüfung der einzelnen GC-Laufzeiten zu verschwenden. Außerdem wird beobachtet, dass die meisten Objekte relativ schnell Müll gesammelt werden. Von nun an nennen wir diese "guten Objekte", seit sie jung sterben. Dies wird als "schwache Generationshypothese" bezeichnet und wurde erstmals in den 60er Jahren beobachtet.

Ein kurzer Hinweis: Im Gegensatz zu den ersten beiden Generationen besteht die Liste der langlebigen dritten Generation nicht aus regelmäßigem Müll. Es wird geprüft, wenn das Verhältnis von langlebigen anstehenden Objekten (diejenigen, die sich in der dritten Generierungsliste befinden, aber noch keinen GC-Zyklus hatten) zu den insgesamt in der Liste befindlichen langlebigen Objekten mehr als 25% beträgt. Dies liegt daran, dass die dritte Liste unbegrenzt ist (Dinge werden nie von einer Liste auf eine andere Liste verschoben, sodass sie nur dann verschwinden, wenn tatsächlich Müll gesammelt wird). Dies bedeutet, dass für Anwendungen, bei denen Sie viele langlebige Objekte erstellen, GC-Zyklen auf der dritten liste kann es recht lang werden. Durch die Verwendung eines Verhältnisses erzielen wir "amortisierte lineare Leistung in der Gesamtzahl der Objekte"; aka, je länger die Liste ist, desto länger dauert GC, aber desto seltener führen wir GC aus (hier ist der ursprüngliche Vorschlag von 2008 für diese Heuristik von Martin von Löwis für weiteres Lesen). Das Ausführen einer Speicherbereinigung für die dritte Generation oder "ausgereifte" Liste wird "vollständige Speicherbereinigung" genannt.

Die generationenbasierte Speicherbereinigung beschleunigt die Abläufe ungemein, da wir nicht nach Objekten suchen müssen, die wahrscheinlich nicht immer GC benötigen, aber wie hilft es uns, zyklische Referenzen zu brechen? Wahrscheinlich nicht sehr gut, stellt sich heraus. Die Funktion für die tatsächlich diese Referenzzyklen zu brechen beginnt wie folgt :

/* Break reference cycles by clearing the containers involved.  This is
 * tricky business as the lists can be changing and we don't know which
 * objects may be freed.  It is possible I screwed something up here.
 */
static void
delete_garbage(PyGC_Head *collectable, PyGC_Head *old)

Der Grund, warum die Garbage Collection für Generationen dabei hilft, ist, dass wir die Länge der Liste als separate Zählung beibehalten können. Jedes Mal, wenn wir der Generation ein neues Objekt hinzufügen, erhöhen wir diese Zählung, und jedes Mal, wenn wir ein Objekt in eine andere Generation verschieben oder es freigeben, dekrementieren wir die Zählung. Theoretisch sollte diese Zählung am Ende eines GC-Zyklus (für die ersten beiden Generationen sowieso immer) immer 0 sein. Andernfalls ist alles, was in der Liste übrig ist, eine Art Zirkelreferenz, und wir können sie löschen. Es gibt jedoch noch ein weiteres Problem: Was ist, wenn die übrig gebliebenen Objekte Pythons magische Methode __del__ ? __del__ wird jedes Mal aufgerufen, wenn ein Python-Objekt zerstört wird. Wenn jedoch zwei Objekte in einem __del__ über __del__ Methoden verfügen, können wir nicht sicher sein, dass die Zerstörung eines __del__ die anderen __del__ __del__ Methoden nicht zerstört. Stellen Sie sich vor, wir hätten folgendes geschrieben:

class A(object):
    def __init__(self, b=None):
        self.b = b
 
    def __del__(self):
        print("We're deleting an instance of A containing:", self.b)
     
class B(object):
    def __init__(self, a=None):
        self.a = a
 
    def __del__(self):
        print("We're deleting an instance of B containing:", self.a)

und wir setzen eine Instanz von A und eine Instanz von B so, dass sie aufeinander zeigen, und dann landen sie im selben Speicherbereinigungszyklus? Nehmen wir an, wir wählen uns zufällig aus und legen zuerst unsere Instanz von A ab. Die __del__ Methode von A wird aufgerufen, es wird gedruckt, dann wird A freigegeben. Als nächstes kommen wir zu B, wir nennen seine __del__ Methode und oops! Segfault! A existiert nicht mehr. Wir könnten dies beheben, indem wir zunächst alle __del__ Methoden aufrufen, die dann übrig __del__ , und dann einen weiteren Durchlauf durchführen, um wirklich alles zu löschen. Dies führt jedoch zu einem anderen Problem: Was __del__ wenn eine __del__ Methode eines Objekts einen Verweis auf das andere Objekt speichert, das gerade gecedet wird Hat uns irgendwo ein Hinweis auf uns? Wir haben immer noch einen Referenzzyklus, aber jetzt ist es nicht möglich, eines der beiden Objekte tatsächlich zu GC, auch wenn sie nicht mehr verwendet werden. Beachten Sie, dass ein Objekt selbst dann, wenn es nicht Teil einer kreisförmigen Datenstruktur ist, in seiner eigenen __del__ Methode __del__ werden kann. Python prüft dies und beendet die GC-Analyse, wenn die Anzahl der Objekte nach dem __del__ der __del__ Methode __del__ ist.

CPython beschäftigt sich damit, diese un-GC-fähigen Objekte (alles, was irgendeine Art von __del__ und eine __del__ Methode hat) auf eine globale Liste von uneinholbarem Müll zu __del__ und sie dann für alle Ewigkeit dort zu lassen:

/* list of uncollectable objects */
static PyObject *garbage = NULL;

Müllsammlung Verwandte Beispiele