Alles, was Sie über den Garbage Collector in Python wissen müssen

Im Allgemeinen müssen Sie sich beim Schreiben von Python-Code keine Gedanken über den Garbage Collector und die Arbeit mit dem Speicher machen. Sobald Objekte nicht mehr benötigt werden, gibt Python automatisch Speicher unter ihnen frei. Wenn Sie jedoch verstehen, wie GC funktioniert, können Sie besseren Code schreiben.

Speichermanager


Im Gegensatz zu anderen gängigen Sprachen gibt Python nicht den gesamten Speicher an das Betriebssystem zurück, sobald ein Objekt gelöscht wird. Stattdessen wird ein zusätzlicher Speichermanager für kleine Objekte verwendet (deren Größe weniger als 512 Byte beträgt). Um mit solchen Objekten arbeiten zu können, weist er große Speicherblöcke zu, in denen in Zukunft viele kleine Objekte gespeichert werden.

Sobald eines der kleinen Objekte gelöscht wird - der Speicher darunter geht nicht an das Betriebssystem, Python lässt es für neue Objekte mit der gleichen Größe. Wenn in einem der zugewiesenen Speicherblöcke keine Objekte mehr vorhanden sind, kann Python diese für das Betriebssystem freigeben. In der Regel werden Blöcke freigegeben, wenn das Skript viele temporäre Objekte erstellt.

Wenn ein langlebiger Python-Prozess im Laufe der Zeit mehr Speicher verbraucht, bedeutet dies keinesfalls, dass Ihr Code ein Problem mit Speicherverlusten aufweist. Wenn Sie mehr über den Speichermanager in Python erfahren möchten, können Sie dies in meinem anderen Artikel nachlesen.

Garbage Collection-Algorithmen


Der Standard-Python-Interpreter (CPython) verwendet zwei Algorithmen gleichzeitig: Referenzzählung und Generations-Garbage-Collector (im Folgenden: GC), besser bekannt als das Standard- GC-Modul von Python.

Der Link-Counting-Algorithmus ist sehr einfach und effizient, hat jedoch einen großen Nachteil. Er weiß nicht, wie man Zirkelverweise definiert. Aus diesem Grund gibt es in Python einen zusätzlichen Kollektor namens Generation GC, der Objekte mit potenziellen Zirkelreferenzen überwacht.

In Python ist der Referenzzählalgorithmus grundlegend und kann nicht deaktiviert werden, während der GC optional ist und deaktiviert werden kann.

Link Counting Algorithmus


Der Link-Counting-Algorithmus ist eine der einfachsten Techniken zur Speicherbereinigung. Objekte werden gelöscht, sobald sie nicht mehr referenziert werden.

In Python speichern Variablen keine Werte, sondern dienen als Verweise auf Objekte. Das heißt, wenn Sie einer neuen Variablen einen Wert zuweisen, wird zuerst ein Objekt mit diesem Wert erstellt, und erst dann beginnt die Variable, darauf zu verweisen. Mehrere Variablen können auf ein einzelnes Objekt verweisen.

Jedes Objekt in Python enthält ein zusätzliches Feld (Referenzzähler), in dem die Anzahl der Links zu diesem Objekt gespeichert ist. Sobald sich jemand auf ein Objekt bezieht, wird dieses Feld um eins erhöht. Wenn der Link aus irgendeinem Grund verschwindet, wird dieses Feld um eins reduziert.

Beispiele, wenn die Anzahl der Links zunimmt:

  • Zuweisungsoperator
  • Argumente übergeben
  • Fügen Sie ein neues Objekt in das Blatt ein (die Anzahl der Links für das Objekt erhöht sich).
  • eine Konstruktion der Form foo = bar (foo beginnt sich auf dasselbe Objekt wie bar zu beziehen)

Sobald der Referenzzähler für ein bestimmtes Objekt Null erreicht, beginnt der Interpreter mit der Zerstörung des Objekts. Wenn das entfernte Objekt Links zu anderen Objekten enthielt, werden diese Links ebenfalls gelöscht. Somit kann das Entfernen eines Objekts das Entfernen anderer Objekte beinhalten.

Wenn beispielsweise eine Liste gelöscht wird, wird die Referenzanzahl in allen Elementen um eins reduziert. Wenn alle Objekte in der Liste nirgendwo anders verwendet werden, werden sie ebenfalls gelöscht.

Variablen, die außerhalb von Funktionen, Klassen und Blöcken deklariert sind, werden als global bezeichnet. In der Regel entspricht der Lebenszyklus solcher Variablen der Lebensdauer des Python-Prozesses. Daher fällt die Anzahl der Verweise auf Objekte, auf die durch globale Variablen verwiesen wird, niemals auf Null.

Variablen, die innerhalb eines Blocks (Funktion, Klasse) deklariert sind, sind lokal sichtbar (d. H. Sie sind nur innerhalb des Blocks sichtbar). Sobald der Python-Interpreter den Block verlässt, werden alle durch lokale Variablen in ihm erstellten Links zerstört.

Sie können die Anzahl der Links sys.getrefcount mit der Funktion sys.getrefcount überprüfen.

Beispiel eines Linkzählers:

 foo = [] # 2 ,    foo    getrefcount print(sys.getrefcount(foo)) def bar(a): # 4  #  foo,   (a), getrefcount       print(sys.getrefcount(a)) bar(foo) # 2 ,      print(sys.getrefcount(foo)) 

Der Hauptgrund, warum der Standardinterpreter (CPython) einen Referenzzähler verwendet, ist historisch. Über diesen Ansatz wird derzeit viel diskutiert. Einige Leute glauben, dass ein Garbage Collector ohne einen Link-Counting-Algorithmus viel effizienter sein kann. Dieser Algorithmus weist viele Probleme auf, z. B. zirkuläre Links, blockierende Threads sowie zusätzlichen Overhead für Speicher und CPU.

Der Hauptvorteil dieses Algorithmus besteht darin, dass Objekte sofort gelöscht werden, sobald sie nicht benötigt werden.

Optionaler Müllsammler


Warum brauchen wir einen zusätzlichen Algorithmus, wenn wir bereits Referenzzählung haben?

Leider hat der klassische Linkzählalgorithmus einen großen Nachteil: Er weiß nicht, wie man kreisförmige Links findet. Loopbacks treten auf, wenn ein oder mehrere Objekte aufeinander verweisen.

Zwei Beispiele:

Bild

Wie Sie sehen können, bezieht sich das lst Objekt auf sich selbst, während sich object1 und object2 beziehen. Für solche Objekte beträgt der Referenzzähler immer 1.

Python-Demo:

 import gc #  ctypes        class PyObject(ctypes.Structure): _fields_ = [("refcnt", ctypes.c_long)] gc.disable() #   GC lst = [] lst.append(lst) #    lst lst_address = id(lst) #   lst del lst object_1 = {} object_2 = {} object_1['obj2'] = object_2 object_2['obj1'] = object_1 obj_address = id(object_1) #   del object_1, object_2 #          # gc.collect() #    print(PyObject.from_address(obj_address).refcnt) print(PyObject.from_address(lst_address).refcnt) 

Im obigen Beispiel entfernt die Anweisung del Verweise auf unsere Objekte (nicht auf die Objekte selbst). Sobald Python eine del-Anweisung ausführt, kann über Python-Code nicht mehr auf diese Objekte zugegriffen werden. Wenn das GC-Modul ausgeschaltet ist, bleiben sie jedoch weiterhin im Speicher Sie hatten Zirkelverweise und ihr Zähler ist immer noch einer. Sie können solche Beziehungen mithilfe der objgraph visuell objgraph .

Um dieses Problem zu beheben, wurde in Python 1.5 ein zusätzlicher Algorithmus hinzugefügt, der als gc-Modul bekannt ist . Die einzige Aufgabe besteht darin, zyklische Objekte zu entfernen, auf die aus dem Code kein Zugriff mehr besteht.

Loopbacks können nur in Containerobjekten auftreten. Das heißt, in Objekten, in denen andere Objekte gespeichert werden können, z. B. Listen, Wörterbücher, Klassen und Tupel. GC verfolgt keine einfachen und unveränderlichen Typen, außer Tupeln. Einige Tupel und Wörterbücher werden ebenfalls von der Verfolgungsliste ausgeschlossen, wenn bestimmte Bedingungen erfüllt sind. Bei allen anderen Objekten wird garantiert, dass der Referenzzählalgorithmus zurechtkommt.

Wenn der GC ausgelöst wird


Im Gegensatz zum Referenzzählalgorithmus arbeitet der zyklische GC nicht in Echtzeit und wird regelmäßig ausgeführt. Jeder Lauf des Kollektors erzeugt Mikropausen im Code, sodass CPython (der Standardinterpreter) verschiedene Heuristiken verwendet, um die Häufigkeit des Garbage Collectors zu bestimmen.

Der zyklische Garbage Collector unterteilt alle Objekte in 3 Generationen (Generationen). Neue Objekte fallen in die erste Generation. Wenn die neue Einrichtung den Speicherbereinigungsprozess überlebt, wechselt sie zur nächsten Generation. Je höher die Generation, desto seltener wird nach Müll gesucht. Da neue Objekte häufig eine sehr kurze Lebensdauer haben (vorübergehend sind), ist es sinnvoll, sie häufiger zu befragen als solche, die bereits mehrere Phasen der Speicherbereinigung durchlaufen haben.

Jede Generation hat einen speziellen Zähler und einen Antwortschwellenwert, bei dessen Erreichen der Speicherbereinigungsprozess ausgelöst wird. Jeder Zähler speichert die Anzahl der Zuweisungen abzüglich der Anzahl der Freigaben in einer bestimmten Generation. Sobald ein Containerobjekt in Python erstellt wurde, werden diese Zähler überprüft. Wenn die Bedingungen funktionieren, beginnt der Speicherbereinigungsprozess.

Wenn mehrere oder mehr Generationen gleichzeitig die Schwelle überschritten haben, wird die älteste Generation ausgewählt. Dies liegt daran, dass ältere Generationen auch alle vorherigen scannen. Um die Anzahl der Speicherbereinigungspausen für langlebige Objekte zu verringern, verfügt die älteste Generation über zusätzliche Bedingungen .

Die Standardschwellenwerte für Generationen sind auf 700, 10 10 Sie können sie jedoch jederzeit mit den gc.get_threshold gc.set_threshold .

Schleifensuchalgorithmus


Für eine vollständige Beschreibung des Schleifensuchalgorithmus ist ein separater Artikel erforderlich. Kurz gesagt, GC iteriert über jedes Objekt der ausgewählten Generationen und entfernt vorübergehend alle Verknüpfungen von einem einzelnen Objekt (alle Verknüpfungen, auf die sich dieses Objekt bezieht). Nach einem vollständigen Durchlauf werden alle Objekte mit einer Linkanzahl von weniger als zwei als von Python nicht zugänglich angesehen und können gelöscht werden.

Für ein tieferes Verständnis empfehle ich, die ursprüngliche Beschreibung des Algorithmus von Neil Schemenauer und die collect aus den CPython-Quellen zu lesen (Anmerkung des Übersetzers: englisches Material). Eine Beschreibung von Quora und ein Beitrag über den Garbage Collector können ebenfalls hilfreich sein.

Es ist erwähnenswert, dass das in der ursprünglichen Beschreibung des Algorithmus beschriebene Problem mit Destruktoren seit Python 3.4 behoben wurde (weitere Details in PEP 442 ).

Optimierungstipps


Schleifen treten häufig in realen Aufgaben auf, sie können in Problemen mit Diagrammen, verknüpften Listen oder in Datenstrukturen auftreten, in denen Sie die Beziehungen zwischen Objekten verfolgen müssen. Wenn Ihr Programm eine hohe Last hat und Verzögerungen erfordert, sollten Schleifen nach Möglichkeit vermieden werden.

An Stellen, an denen Sie wissentlich zirkuläre Links verwenden, können Sie "schwache" Links verwenden. Schwache Links sind im Schwachstellenmodul implementiert und wirken sich im Gegensatz zu regulären Links in keiner Weise auf den Linkzähler aus . Wenn sich herausstellt, dass das Objekt mit schwachen Referenzen gelöscht wurde, None stattdessen None zurückgegeben.

In einigen Fällen ist es hilfreich, die automatische Erstellung durch das GC-Modul zu deaktivieren und manuell aufzurufen. Rufen gc.disable() einfach gc.disable() und rufen gc.collect() dann gc.collect() manuell auf.

So finden und debuggen Sie zirkuläre Links


Das Debuggen von Schleifen kann schmerzhaft sein, insbesondere wenn Ihr Code viele Module von Drittanbietern verwendet.

Das gc-Modul bietet Hilfsfunktionen, die beim Debuggen helfen können. Wenn die GC-Parameter auf das Flag DEBUG_SAVEALL , werden alle unzugänglichen Objekte zur Liste gc.garbage hinzugefügt.

 import gc gc.set_debug(gc.DEBUG_SAVEALL) print(gc.get_count()) lst = [] lst.append(lst) list_id = id(lst) del lst gc.collect() for item in gc.garbage: print(item) assert list_id == id(item) 

Sobald Sie den Problempunkt identifiziert haben, kann er mit objgraph visualisiert werden.

Bild

Fazit

Der Haupt-Garbage-Collection-Prozess wird von einem Link-Counting-Algorithmus ausgeführt, der sehr einfach ist und keine Einstellungen enthält. Ein zusätzlicher Algorithmus wird nur zum Suchen und Löschen von Objekten mit Zirkelverweisen verwendet.

Sie sollten den Code für den Garbage Collector nicht vorzeitig optimieren. In der Praxis sind Probleme mit der Garbage Collection eher selten.

PS: Ich bin der Autor dieses Artikels, Sie können alle Fragen stellen.

Source: https://habr.com/ru/post/de417215/


All Articles