Wie kann ich explizit Speicher in Python freigeben?

387

Ich habe ein Python-Programm geschrieben, das auf eine große Eingabedatei reagiert, um einige Millionen Objekte zu erstellen, die Dreiecke darstellen. Der Algorithmus lautet:

  1. Lesen Sie eine Eingabedatei
  2. Verarbeiten Sie die Datei und erstellen Sie eine Liste der Dreiecke, die durch ihre Scheitelpunkte dargestellt werden
  3. Geben Sie die Scheitelpunkte im AUS-Format aus: eine Liste der Scheitelpunkte, gefolgt von einer Liste der Dreiecke. Die Dreiecke werden durch Indizes in der Liste der Eckpunkte dargestellt

Die Anforderung von AUS, dass ich die vollständige Liste der Eckpunkte ausdrucken muss, bevor ich die Dreiecke drucke, bedeutet, dass ich die Liste der Dreiecke im Speicher halten muss, bevor ich die Ausgabe in eine Datei schreibe. In der Zwischenzeit erhalte ich aufgrund der Größe der Listen Speicherfehler.

Was ist der beste Weg, um Python mitzuteilen, dass ich einige Daten nicht mehr benötige und sie freigegeben werden können?

Nathan Fellman
quelle
11
Drucken Sie die Dreiecke in einer Zwischendatei aus und lesen Sie sie bei Bedarf erneut ein.
Alice Purcell
2
Diese Frage könnte möglicherweise zwei ganz unterschiedliche Dinge betreffen. Sind diese Fehler vom selben Python-Prozess , in welchem ​​Fall wir uns darum kümmern, Speicher auf dem Heap des Python-Prozesses freizugeben, oder stammen sie von verschiedenen Prozessen auf dem System, in welchem ​​Fall wir uns darum kümmern, Speicher für das Betriebssystem freizugeben?
Charles Duffy

Antworten:

455

Laut der offiziellen Python-Dokumentation können Sie den Garbage Collector zwingen, nicht referenzierten Speicher mit freizugeben gc.collect(). Beispiel:

import gc
gc.collect()
Zufluchtsort
quelle
19
Die Dinge werden sowieso häufig gesammelt, außer in einigen ungewöhnlichen Fällen, also denke ich nicht, dass das viel helfen wird.
Lennart Regebro
24
Im Allgemeinen ist gc.collect () zu vermeiden. Der Müllsammler weiß, wie er seine Arbeit macht. Das heißt, wenn sich das OP in einer Situation befindet, in der er plötzlich viele Objekte freigibt (wie in Millionenhöhe), kann sich gc.collect als nützlich erweisen.
Jason Baker
165
Wenn gc.collect()Sie sich am Ende einer Schleife aufrufen, können Sie eine Fragmentierung des Speichers vermeiden, was wiederum dazu beiträgt, die Leistung aufrechtzuerhalten. Ich habe gesehen, dass dies einen signifikanten Unterschied macht (~ 20% Laufzeit IIRC)
RobM
39
Ich benutze Python 3.6. Das Aufrufen gc.collect()nach dem Laden eines Pandas-Datenrahmens von HDF5 (500.000 Zeilen) reduzierte die Speichernutzung von 1,7 GB auf 500 MB
John
15
Ich muss mehrere numpy Arrays von 25 GB in einem System mit 32 GB Speicher laden und verarbeiten. Die Verwendung del my_arraygefolgt von gc.collect()nach der Verarbeitung des Arrays ist die einzige Möglichkeit, den Speicher tatsächlich freizugeben, und mein Prozess überlebt, um das nächste Array zu laden.
David
113

Leider (abhängig von Ihrer Version und Version von Python) verwenden einige Objekttypen "freie Listen", die eine ordentliche lokale Optimierung darstellen, jedoch zu einer Speicherfragmentierung führen können, insbesondere indem immer mehr Speicher nur für Objekte eines bestimmten Typs und "vorgesehen" wird damit für den "allgemeinen Fonds" nicht verfügbar.

Die einzige wirklich zuverlässige Möglichkeit, um sicherzustellen, dass eine große, aber vorübergehende Speichernutzung alle Ressourcen an das System zurückgibt, besteht darin, diese Nutzung in einem Unterprozess durchzuführen, der die speicherhungrige Arbeit erledigt und dann beendet. Unter solchen Bedingungen erledigt das Betriebssystem seine Aufgabe und recycelt gerne alle Ressourcen, die der Unterprozess möglicherweise verschlungen hat. Glücklicherweise multiprocessingmacht das Modul diese Art von Operation (die früher eher schmerzhaft war) in modernen Versionen von Python nicht schlecht.

In Ihrem Anwendungsfall scheint es der beste Weg für die Unterprozesse zu sein, einige Ergebnisse zu akkumulieren und dennoch sicherzustellen, dass diese Ergebnisse für den Hauptprozess verfügbar sind, die Verwendung von semi-temporären Dateien (mit semi-temporär meine ich, NICHT die Art von Dateien, die beim Schließen automatisch verschwinden, nur gewöhnliche Dateien, die Sie explizit löschen, wenn Sie damit fertig sind).

Alex Martelli
quelle
31
Ich würde mir sicher ein triviales Beispiel dafür wünschen.
Aaron Hall
3
Ernsthaft. Was @AaronHall gesagt hat.
Noob Saibot
17
@ AaronHall Triviales Beispiel jetzt verfügbar , bei dem multiprocessing.Manageranstelle von Dateien der gemeinsam genutzte Status implementiert wird.
user4815162342
48

Die delAnweisung könnte von Nutzen sein, aber beim IIRC kann nicht garantiert werden, dass der Speicher freigegeben wird . Die Dokumente sind hier ... und warum es nicht veröffentlicht wird, finden Sie hier .

Ich habe Leute auf Linux- und Unix-Systemen gehört, die einen Python-Prozess gezwungen haben, etwas zu arbeiten, Ergebnisse zu erzielen und ihn dann zu beenden.

Dieser Artikel enthält Hinweise zum Python-Garbage-Collector, aber ich denke, mangelnde Speicherkontrolle ist der Nachteil des verwalteten Speichers

Aiden Bell
quelle
Wären IronPython und Jython eine weitere Option, um dieses Problem zu vermeiden?
Esteban Küber
@ Voyager: Nein, würde es nicht. Und auch keine andere Sprache. Das Problem ist, dass er große Datenmengen in eine Liste einliest und die Daten für den Speicher zu groß sind.
Lennart Regebro
1
Unter IronPython oder Jython wäre es wahrscheinlich schlimmer . In diesen Umgebungen kann nicht einmal garantiert werden, dass der Speicher freigegeben wird, wenn nichts anderes eine Referenz enthält.
Jason Baker
@voyager, ja, da die Java Virtual Machine global nach freiem Speicher sucht. Für die JVM ist Jython nichts Besonderes. Auf der anderen Seite hat die JVM ihre eigenen Nachteile, zum Beispiel, dass Sie im Voraus angeben müssen, wie viel Heap sie verwenden kann.
Prof. Falken Vertrag verletzt
32

Python wird durch Müll gesammelt. Wenn Sie also die Größe Ihrer Liste reduzieren, wird Speicherplatz zurückgewonnen. Sie können auch die Anweisung "del" verwenden, um eine Variable vollständig zu entfernen:

biglist = [blah,blah,blah]
#...
del biglist
Ned Batchelder
quelle
18
Das ist und ist nicht wahr. Wenn Sie die Größe der Liste verringern, kann der Speicher zwar wiederhergestellt werden, es gibt jedoch keine Garantie dafür, wann dies geschehen wird.
user142350
3
Nein, aber normalerweise hilft es. Wie ich die Frage hier verstehe, besteht das Problem jedoch darin, dass er so viele Objekte haben muss, dass ihm der Speicher ausgeht, bevor er sie alle verarbeitet, wenn er sie in eine Liste einliest. Das Löschen der Liste vor Abschluss der Verarbeitung ist wahrscheinlich keine nützliche Lösung. ;)
Lennart Regebro
3
Würde ein Zustand mit wenig Speicher / zu wenig Speicher nicht einen "Notlauf" des Garbage Collectors auslösen?
Jeremy Friesner
4
wird biglist = [] Speicher freigeben?
Neouyghur
3
Ja, wenn die alte Liste von nichts anderem referenziert wird.
Ned Batchelder
22

Sie können den Speicher nicht explizit freigeben. Sie müssen lediglich sicherstellen, dass keine Verweise auf Objekte vorhanden sind. Sie werden dann Müll gesammelt, wodurch der Speicher frei wird.

Wenn Sie in Ihrem Fall große Listen benötigen, müssen Sie den Code normalerweise neu organisieren und stattdessen Generatoren / Iteratoren verwenden. Auf diese Weise müssen Sie die großen Listen überhaupt nicht im Speicher haben.

http://www.prasannatech.net/2009/07/introduction-python-generators.html

Lennart Regebro
quelle
1
Wenn dieser Ansatz machbar ist, lohnt es sich wahrscheinlich. Es sollte jedoch beachtet werden, dass Sie keinen zufälligen Zugriff auf Iteratoren ausführen können, was zu Problemen führen kann.
Jason Baker
Das stimmt, und wenn dies erforderlich ist, erfordert der zufällige Zugriff auf große Datenmengen wahrscheinlich eine Art Datenbank.
Lennart Regebro
Sie können einfach einen Iterator verwenden, um eine zufällige Teilmenge eines anderen Iterators zu extrahieren.
S.Lott
Stimmt, aber dann müssten Sie alles durchlaufen, um die Teilmenge zu erhalten, die sehr langsam sein wird.
Lennart Regebro
21

( delKann Ihr Freund sein, da Objekte als löschbar markiert werden, wenn keine anderen Verweise darauf vorhanden sind. Der CPython-Interpreter behält diesen Speicher häufig für die spätere Verwendung bei, sodass Ihr Betriebssystem den "freigegebenen" Speicher möglicherweise nicht sieht.)

Möglicherweise würden Sie überhaupt nicht auf Speicherprobleme stoßen, wenn Sie eine kompaktere Struktur für Ihre Daten verwenden. Daher sind Listen mit Zahlen viel weniger speichereffizient als das Format, das vom Standardmodul arrayoder vom numpyModul eines Drittanbieters verwendet wird. Sie würden Speicher sparen, indem Sie Ihre Scheitelpunkte in ein NumPy 3xN-Array und Ihre Dreiecke in ein N-Element-Array einfügen.

Eric O Lebigot
quelle
Eh? Die Garbage Collection von CPython basiert auf Nachzählungen. Es ist kein periodisches Mark-and-Sweep (wie bei vielen gängigen JVM-Implementierungen), sondern löscht sofort etwas, sobald der Referenzzähler Null erreicht. Nur Zyklen (bei denen Nachzählungen Null wären, aber nicht aufgrund von Schleifen im Referenzbaum) müssen regelmäßig gewartet werden. deltut nichts, was es nicht tun würde, allen Namen, die auf ein Objekt verweisen, einen anderen Wert zuzuweisen.
Charles Duffy
Ich sehe, woher du kommst: Ich werde die Antwort entsprechend aktualisieren. Ich verstehe, dass der CPython-Interpreter tatsächlich auf eine Zwischenmethode funktioniert: delBefreit den Speicher aus Sicht von Python, aber im Allgemeinen nicht aus Sicht der C-Laufzeitbibliothek oder des Betriebssystems. Referenzen: stackoverflow.com/a/32167625/4297 , effbot.org/pyfaq/… .
Eric O Lebigot
Sie sind sich über den Inhalt Ihrer Links einig, aber wenn das OP von einem Fehler spricht, den sie vom selben Python-Prozess erhalten , scheint die Unterscheidung zwischen der Freigabe von Speicher für den prozesslokalen Heap und für das Betriebssystem wahrscheinlich nicht relevant zu sein ( Durch die Freigabe für den Heap wird dieser Speicherplatz für neue Zuweisungen innerhalb dieses Python-Prozesses verfügbar. Und das delist gleichermaßen effektiv bei Exits-from-Scope, Neuzuweisungen usw.
Charles Duffy
11

Ich hatte ein ähnliches Problem beim Lesen eines Diagramms aus einer Datei. Die Verarbeitung umfasste die Berechnung einer 200 000 x 200 000 Float-Matrix (zeilenweise), die nicht in den Speicher passte. Der Versuch, den Speicher zwischen den Berechnungen freizugeben, gc.collect()behebt den speicherbezogenen Aspekt des Problems, führte jedoch zu Leistungsproblemen: Ich weiß nicht warum, aber obwohl die Menge des verwendeten Speichers konstant blieb, gc.collect()dauerte jeder neue Aufruf etwas länger als Der vorherige. Das Sammeln von Müll nahm also ziemlich schnell die meiste Rechenzeit in Anspruch.

Um sowohl die Speicher- als auch die Leistungsprobleme zu beheben, habe ich auf die Verwendung eines Multithreading-Tricks umgestellt, den ich einmal irgendwo gelesen habe (es tut mir leid, ich kann den entsprechenden Beitrag nicht mehr finden). Bevor ich jede Zeile der Datei in einer großen forSchleife las , verarbeitete und ab gc.collect()und zu ausführte, um Speicherplatz freizugeben. Jetzt rufe ich eine Funktion auf, die einen Teil der Datei in einem neuen Thread liest und verarbeitet. Sobald der Thread endet, wird der Speicher automatisch ohne das seltsame Leistungsproblem freigegeben.

Praktisch funktioniert es so:

from dask import delayed  # this module wraps the multithreading
def f(storage, index, chunk_size):  # the processing function
    # read the chunk of size chunk_size starting at index in the file
    # process it using data in storage if needed
    # append data needed for further computations  to storage 
    return storage

partial_result = delayed([])  # put into the delayed() the constructor for your data structure
# I personally use "delayed(nx.Graph())" since I am creating a networkx Graph
chunk_size = 100  # ideally you want this as big as possible while still enabling the computations to fit in memory
for index in range(0, len(file), chunk_size):
    # we indicates to dask that we will want to apply f to the parameters partial_result, index, chunk_size
    partial_result = delayed(f)(partial_result, index, chunk_size)

    # no computations are done yet !
    # dask will spawn a thread to run f(partial_result, index, chunk_size) once we call partial_result.compute()
    # passing the previous "partial_result" variable in the parameters assures a chunk will only be processed after the previous one is done
    # it also allows you to use the results of the processing of the previous chunks in the file if needed

# this launches all the computations
result = partial_result.compute()

# one thread is spawned for each "delayed" one at a time to compute its result
# dask then closes the tread, which solves the memory freeing issue
# the strange performance issue with gc.collect() is also avoided
Retzod
quelle
1
Ich frage mich, warum Sie in Python `//` `s anstelle von # für Kommentare verwenden.
JC Rocamonde
Ich wurde zwischen Sprachen verwechselt. Vielen Dank für die Bemerkung, ich habe die Syntax aktualisiert.
Retzod
9

Andere haben einige Möglichkeiten veröffentlicht, wie Sie den Python-Interpreter möglicherweise dazu bringen können, den Speicher freizugeben (oder auf andere Weise Speicherprobleme zu vermeiden). Wahrscheinlich sollten Sie zuerst ihre Ideen ausprobieren. Ich halte es jedoch für wichtig, Ihnen eine direkte Antwort auf Ihre Frage zu geben.

Es gibt keine Möglichkeit, Python direkt anzuweisen, Speicherplatz freizugeben. Tatsache ist, dass Sie eine Erweiterung in C oder C ++ schreiben müssen, wenn Sie ein so geringes Maß an Kontrolle wünschen.

Es gibt jedoch einige Tools, die dabei helfen können:

Jason Baker
quelle
3
gc.collect () und del gc.garbage [:] funktionieren einwandfrei, wenn ich viel Speicher verwende
Andrew Scott Evans
3

Wenn Sie sich nicht für die Wiederverwendung von Scheitelpunkten interessieren, können Sie zwei Ausgabedateien verwenden - eine für Scheitelpunkte und eine für Dreiecke. Fügen Sie dann die Dreiecksdatei an die Scheitelpunktdatei an, wenn Sie fertig sind.

Nosredna
quelle
1
Ich glaube, ich kann nur die Scheitelpunkte im Speicher behalten und die Dreiecke in einer Datei ausdrucken und dann die Scheitelpunkte erst am Ende ausdrucken. Das Schreiben der Dreiecke in eine Datei ist jedoch ein enormer Leistungsverlust. Gibt es eine Möglichkeit zu beschleunigen , dass die nach oben?
Nathan Fellman