Suche…


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;

Referenzzählung

Die überwiegende Mehrheit der Python-Speicherverwaltung wird mit Referenzzählung ausgeführt.

Jedes Mal, wenn ein Objekt referenziert wird (z. B. einer Variablen zugewiesen) wird der Referenzzähler automatisch erhöht. Wenn dereferenziert wird (z. B. die Variable verlässt den Gültigkeitsbereich), wird die Referenzanzahl automatisch verringert.

Wenn der Referenzzähler Null erreicht, wird das Objekt sofort zerstört und der Speicher wird sofort freigegeben. In der Mehrzahl der Fälle wird der Müllsammler also nicht benötigt.

>>> import gc; gc.disable()  # disable garbage collector
>>> class Track:
        def __init__(self):
            print("Initialized")
        def __del__(self):
            print("Destructed")
>>> def foo():
        Track()
        # destructed immediately since no longer has any references
        print("---")
        t = Track()
        # variable is referenced, so it's not destructed yet
        print("---")
        # variable is destructed when function exits
>>> foo()
Initialized
Destructed
---
Initialized
---
Destructed

Um das Konzept der Referenzen weiter zu demonstrieren:

>>> def bar():
        return Track()
>>> t = bar()
Initialized
>>> another_t = t  # assign another reference
>>> print("...")
...
>>> t = None          # not destructed yet - another_t still refers to it
>>> another_t = None  # final reference gone, object is destructed
Destructed

Speicherbereiniger für Referenzzyklen

Der Garbage Collector wird nur benötigt, wenn Sie einen Referenzzyklus haben . Das einfache Beispiel eines Referenzzyklus ist einer, in dem A sich auf B bezieht und B sich auf A bezieht, während sich nichts anderes auf A oder B bezieht. Ihre Referenzzählung ist jedoch 1 und kann daher nicht allein durch den Referenzzählalgorithmus freigegeben werden.

>>> import gc; gc.disable()  # disable garbage collector
>>> class Track:
        def __init__(self):
            print("Initialized")
        def __del__(self):
            print("Destructed")
>>> A = Track()
Initialized
>>> B = Track()
Initialized
>>> A.other = B
>>> B.other = A
>>> del A; del B  # objects are not destructed due to reference cycle
>>> gc.collect()  # trigger collection
Destructed
Destructed
4

Ein Referenzzyklus kann beliebig lang sein. Wenn A auf B zeigt, zeigt B auf C, zeigt auf ..., zeigt auf Z, was auf A zeigt, dann werden bis zur Speicherbereinigungsphase weder A bis Z gesammelt.

>>> objs = [Track() for _ in range(10)]
Initialized
Initialized
Initialized
Initialized
Initialized
Initialized
Initialized
Initialized
Initialized
Initialized
>>> for i in range(len(objs)-1):
...     objs[i].other = objs[i + 1]
...
>>> objs[-1].other = objs[0]  # complete the cycle
>>> del objs                  # no one can refer to objs now - still not destructed
>>> gc.collect()
Destructed
Destructed
Destructed
Destructed
Destructed
Destructed
Destructed
Destructed
Destructed
Destructed
20

Auswirkungen des Befehls del

Entfernen eines Variablennamens aus dem Gültigkeitsbereich mit del v oder Entfernen eines Objekts aus einer Auflistung mit del v[item] oder del[i:j] oder Entfernen eines Attributs mit del v.name oder einer anderen Möglichkeit zum Entfernen von Verweisen auf ein Objekt, löst keine destructor Anrufe oder jeder Speicher in sich selbst befreit. Objekte werden nur zerstört, wenn ihre Referenzzählung null erreicht.

>>> import gc
>>> gc.disable()  # disable garbage collector
>>> class Track:
        def __init__(self):
            print("Initialized")
        def __del__(self):
            print("Destructed")
>>> def bar():
    return Track()
>>> t = bar()
Initialized
>>> another_t = t  # assign another reference
>>> print("...")
...
>>> del t          # not destructed yet - another_t still refers to it
>>> del another_t  # final reference gone, object is destructed
Destructed

Wiederverwendung von primitiven Objekten

Eine interessante Sache, die zur Optimierung Ihrer Anwendungen beitragen kann, ist, dass Primitive auch unter der Haube neu gezählt werden. Schauen wir uns die Zahlen an. Für alle Ganzzahlen zwischen -5 und 256 verwendet Python immer dasselbe Objekt:

>>> import sys
>>> sys.getrefcount(1)
797
>>> a = 1
>>> b = 1
>>> sys.getrefcount(1)
799

Beachten Sie, dass der Refcount zunimmt, was bedeutet, dass a und b auf dasselbe zugrunde liegende Objekt verweisen, wenn sie sich auf das Primitiv 1 beziehen. Bei größeren Zahlen verwendet Python das zugrunde liegende Objekt jedoch nicht wieder:

>>> a = 999999999
>>> sys.getrefcount(999999999)
3
>>> b = 999999999
>>> sys.getrefcount(999999999)
3

Da sich der refcount für 999999999 nicht ändert, wenn er a und b zugewiesen wird, können wir daraus schließen, dass sie sich auf zwei verschiedene zugrunde liegende Objekte beziehen, obwohl ihnen beide das gleiche 999999999 zugewiesen ist.

Anzeige der Refcount eines Objekts

>>> import sys
>>> a = object()
>>> sys.getrefcount(a)
2
>>> b = a
>>> sys.getrefcount(a)
3
>>> del b
>>> sys.getrefcount(a)
2

Objekte zwangsweise freigeben

Sie können die Freigabe von Objekten erzwingen, auch wenn ihr Refcount in Python 2 und 3 nicht 0 ist.

Beide Versionen verwenden dazu das Modul ctypes .

WARNUNG: Wenn Sie dies tun, wird Ihre Python-Umgebung instabil und zum Absturz neigen, ohne dass ein Traceback erforderlich ist! Die Verwendung dieser Methode kann auch zu Sicherheitsproblemen führen (ziemlich unwahrscheinlich). Heben Sie nur Objekte auf, von denen Sie sicher sind, dass Sie sie nie wieder referenzieren. Je.

Python 3.x 3.0
import ctypes
deallocated = 12345
ctypes.pythonapi._Py_Dealloc(ctypes.py_object(deallocated))
Python 2.x 2.3
import ctypes, sys
deallocated = 12345
(ctypes.c_char * sys.getsizeof(deallocated)).from_address(id(deallocated))[:4] = '\x00' * 4

Nach dem Ausführen führt jeder Verweis auf das jetzt freigegebene Objekt dazu, dass Python entweder undefiniertes Verhalten erzeugt oder abstürzt - ohne Traceback. Es gab wahrscheinlich einen Grund, warum der Müllsammler das Objekt nicht entfernt hat ...

Wenn Sie None Zuordnung None , wird eine spezielle Meldung Fatal Python error: deallocating None - Fatal Python error: deallocating None vor dem Absturz freigeben.

Verwalten der Speicherbereinigung

Es gibt zwei Ansätze, um zu beeinflussen, wann eine Speicherbereinigung durchgeführt wird. Sie beeinflussen, wie oft der automatische Prozess durchgeführt wird und der andere Prozess manuell eine Bereinigung auslöst.

Der Speicherbereiniger kann manipuliert werden, indem die Sammelschwellen eingestellt werden, die die Frequenz beeinflussen, mit der der Kollektor abläuft. Python verwendet ein generationsbasiertes Speicherverwaltungssystem. Neue Objekte werden in der neuesten Generation - Generation 0 gespeichert, und mit jeder erhaltenen Sammlung werden Objekte zu älteren Generationen befördert. Nach Erreichen der letzten Generation - Generation2 werden sie nicht mehr befördert.

Die Schwellenwerte können mit dem folgenden Snippet geändert werden:

import gc
gc.set_threshold(1000, 100, 10) # Values are just for demonstration purpose

Das erste Argument repräsentiert den Schwellenwert für das Sammeln der Generierung0 . Jedes Mal, wenn die Anzahl der Zuweisungen die Anzahl der Aufhebungen um 1000 überschreitet, wird der Garbage Collection aufgerufen.

Die älteren Generationen werden nicht bei jedem Durchlauf bereinigt, um den Prozess zu optimieren. Das zweite und das dritte Argument sind optional und steuern, wie oft ältere Generationen gereinigt werden. Wenn die Generierung 0 100 Mal ohne Bereinigung der Generierung1 verarbeitet wurde , wird die Generierung1 verarbeitet. In ähnlicher Weise werden die Objekte in generation2 nur verarbeitet werden , wenn die , die in generation1 10 mal gereinigt wurden ohne generation2 zu berühren.

Eine manuelle Einstellung der Schwellenwerte ist von Vorteil, wenn das Programm viele kleine Objekte zuweist, ohne sie aufzuheben, was dazu führt, dass der Garbage Collector zu oft ausgeführt wird (jede Objektzuordnung der Generation 0_Threshold). Obwohl der Collector ziemlich schnell ist, stellt er bei einer großen Anzahl von Objekten ein Leistungsproblem dar. Wie auch immer, es gibt keine einheitliche Strategie für die Auswahl der Schwellenwerte und die Zuverlässigkeit des Anwendungsfalls.

Das manuelle Auslösen einer Sammlung kann wie im folgenden Snippet durchgeführt werden:

import gc
gc.collect()

Die Speicherbereinigung wird automatisch basierend auf der Anzahl der Zuweisungen und Aufhebungen ausgelöst, nicht auf dem verbrauchten oder verfügbaren Speicher. Wenn Sie mit großen Objekten arbeiten, kann der Speicher erschöpft sein, bevor die automatische Bereinigung ausgelöst wird. Dies ist ein guter Anwendungsfall für das manuelle Aufrufen des Speicherbereinigers.

Obwohl es möglich ist, ist es keine ermutigte Praxis. Die Vermeidung von Speicherverlusten ist die beste Option. In großen Projekten kann das Erkennen des Speicherverlusts jedoch eine schwierige Aufgabe sein, und das manuelle Auslösen einer Speicherbereinigung kann als schnelle Lösung bis zum weiteren Debugging verwendet werden.

Bei lang laufenden Programmen kann die Speicherbereinigung nach Zeit oder Ereignis ausgelöst werden. Ein Beispiel für den ersten ist ein Webserver, der nach einer festgelegten Anzahl von Anforderungen eine Sammlung auslöst. Für letzteren einen Webserver, der eine Garbage Collection auslöst, wenn ein bestimmter Anforderungstyp empfangen wird.

Warten Sie nicht, bis die Müllsammlung aufgeräumt ist

Die Tatsache, dass die Speicherbereinigung aufgeräumt wird, bedeutet nicht, dass Sie warten müssen, bis der Speicherbereinigungszyklus aufgeräumt ist.

Insbesondere sollten Sie nicht warten, bis die Garbage Collection Dateihandles, Datenbankverbindungen und Netzwerkverbindungen geschlossen hat.

zum Beispiel:

Im folgenden Code wird davon ausgegangen, dass die Datei beim nächsten Garbage Collection-Zyklus geschlossen wird, wenn f der letzte Verweis auf die Datei war.

>>> f = open("test.txt")
>>> del f

Eine explizitere Methode zum Bereinigen ist das Aufrufen von f.close() . Sie können es noch eleganter machen, indem Sie die with Anweisung verwenden, die auch als Kontextmanager bezeichnet wird :

>>> with open("test.txt") as f:
...     pass
...     # do something with f
>>> #now the f object still exists, but it is closed

Mit der with Anweisung können Sie Ihren Code unter der geöffneten Datei einrücken. Dies macht es explizit und einfacher zu sehen, wie lange eine Datei geöffnet bleibt. Es schließt auch immer eine Datei, auch wenn im while Block eine Ausnahme ausgelöst wird.



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow