9 Stimmen

Python: Das Verhalten des Garbage Collectors

Ich habe eine Django-Anwendung, die einige seltsame Garbage Collection Verhalten zeigt. Es gibt eine Ansicht im Besonderen, die die VM-Größe jedes Mal, wenn sie aufgerufen wird, deutlich erhöht - bis zu einer bestimmten Grenze, an der die Nutzung wieder zurückfällt. Das Problem ist, dass es beträchtliche Zeit in Anspruch nimmt, bis dieser Punkt erreicht ist, und tatsächlich hat die virtuelle Maschine, auf der meine Anwendung läuft, nicht genug Speicher für alle FCGI-Prozesse, um so viel Speicher zu belegen, wie sie dann manchmal tun.

Ich habe die letzten zwei Tage damit verbracht, dies zu untersuchen und etwas über die Python-Garbage-Collection zu lernen, und ich glaube, ich verstehe jetzt, was passiert - zum größten Teil. Bei der Verwendung von

gc.set_debug(gc.DEBUG_STATS)

Für eine einzelne Anfrage erhalte ich dann folgende Ausgabe:

>>> c = django.test.Client()
>>> c.get('/the/view/')
gc: collecting generation 0...
gc: objects in each generation: 724 5748 147341
gc: done.
gc: collecting generation 0...
gc: objects in each generation: 731 6460 147341
gc: done.
[...more of the same...]    
gc: collecting generation 1...
gc: objects in each generation: 718 8577 147341
gc: done.
gc: collecting generation 0...
gc: objects in each generation: 714 0 156614
gc: done.
[...more of the same...]
gc: collecting generation 0...
gc: objects in each generation: 715 5578 156612
gc: done.

Im Wesentlichen wird also eine große Menge von Objekten zugewiesen, die zunächst in die Generation 1 verschoben werden, und wenn die Generation 1 während derselben Anfrage durchsucht wird, werden sie in die Generation 2 verschoben. Wenn ich danach ein manuelles gc.collect(2) ausführe, werden sie entfernt. Und, wie bereits erwähnt, werden sie auch entfernt, wenn der nächste automatische Sweep der Generation 2 stattfindet, was, wenn ich es richtig verstehe, in diesem Fall etwa alle 10 Anfragen der Fall ist (zu diesem Zeitpunkt benötigt die App etwa 150 MB).

Zunächst dachte ich, dass bei der Bearbeitung einer Anfrage eine zyklische Referenzierung stattfindet, die verhindert, dass eines dieser Objekte bei der Bearbeitung dieser Anfrage erfasst wird. Ich habe jedoch Stunden damit verbracht, mit pympler.muppy und objgraph, sowohl nach als auch durch Debugging innerhalb der Anfrageverarbeitung, ein solches zu finden, und es scheint keine zu geben. Vielmehr scheint es, dass die etwa 14.000 Objekte, die während der Anfrage erstellt werden, alle innerhalb einer Referenzkette zu einem Anfrage-globalen Objekt liegen, d.h. sobald die Anfrage verschwindet, können sie freigegeben werden.

Das war jedenfalls mein Versuch, es zu erklären. Allerdings, wenn das wahr ist, und es gibt in der Tat keine zyklischen Abhängigkeiten, sollte nicht der gesamte Baum von Objekten freigegeben werden, sobald was auch immer Anfrage-Objekt, das verursacht, dass sie gehalten werden, geht weg, ohne dass der Garbage Collector beteiligt ist, rein aufgrund der Referenz zählt fallen auf Null?

Mit diesen Voraussetzungen habe ich folgende Fragen:

  • Macht das oben Gesagte überhaupt Sinn, oder muss ich das Problem woanders suchen? Ist es nur ein unglücklicher Zufall, dass wichtige Daten in diesem speziellen Anwendungsfall so lange gespeichert werden?

  • Kann ich etwas tun, um das Problem zu vermeiden? Ich sehe bereits einige Möglichkeiten, die Ansicht zu optimieren, aber das scheint eine Lösung mit begrenztem Umfang zu sein - obwohl ich nicht sicher bin, was ich generisch wäre, entweder; wie ratsam ist es zum Beispiel, gc.collect() oder gc.set_threshold() manuell aufzurufen?

Was die Funktionsweise des Garbage Collectors selbst betrifft:

  • Verstehe ich das richtig, dass ein Objekt immer in die nächste Generation verschoben wird, wenn ein Sweep es untersucht und feststellt, dass die Referenzen, die es hat, nicht zyklisch , sondern kann tatsächlich auf ein Root-Objekt zurückgeführt werden.

  • Was passiert, wenn der gc einen Sweep der, sagen wir, Generation 1 durchführt und ein Objekt findet, das von einem Objekt innerhalb der Generation 2 referenziert wird; folgt er dieser Beziehung innerhalb der Generation 2, oder wartet er auf einen Sweep der Generation 2, bevor er die Situation analysiert?

  • Wenn ich gc.DEBUG_STATS verwende, interessiere ich mich in erster Linie für die Informationen über "Objekte in jeder Generation"; allerdings erhalte ich ständig Hunderte von Meldungen wie "gc: 0.0740s elapsed.", "gc: 1258233035.9370s elapsed."; sie sind total lästig - es dauert sehr lange, bis sie ausgedruckt werden, und sie erschweren es, die interessanten Dinge zu finden. Gibt es eine Möglichkeit, sie loszuwerden?

  • Ich nehme nicht an, dass es eine Möglichkeit gibt, gc.get_objects() nach Generationen zu sortieren, d.h. zum Beispiel nur die Objekte von Generation 2 abzurufen?

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X