Wie schreibt man ein speichersparendes Python-Programm?

8

Es wird gesagt, dass Python den Speicher automatisch verwaltet. Ich bin verwirrt, weil ich ein Python-Programm konsistent mehr als 2 GB Speicher verwendet.

Es ist ein einfacher Multi-Thread-Binärdaten-Downloader und -Entpacker.

%Vor%

Es laufen 15 Threads. Jede Anfrage erhält 15 MB Daten und entpackt sie und speichert sie in einer lokalen Textdatei. Wie könnte dieses Programm mehr als 2 GB Speicher verbrauchen? Muss ich in diesem Fall Speicher-Recycling-Jobs ausführen? Wie kann ich sehen, wie viel Speicher jede Objekte oder Funktionen verwenden?

Ich würde mich über all Ihre Ratschläge oder Tipps freuen, wie Sie ein Python-Programm in einem speichereffizienten Modus ausführen können.

Bearbeiten: Hier ist die Ausgabe von "cat / proc / meminfo"

%Vor%     
jack 02.11.2009, 06:08
quelle

8 Antworten

10

Wie andere gesagt haben, benötigen Sie mindestens die folgenden zwei Änderungen:

  1. Erstellen Sie keine riesige Liste von ganzen Zahlen mit range

    %Vor%
  2. Erstellen Sie keine riesige Zeichenfolge als den gesamten Dateikörper, der gleichzeitig geschrieben werden soll

    %Vor%

Noch besser, Sie könnten die Datei wie folgt schreiben:

%Vor%     
tzot 02.11.2009, 09:40
quelle
8

Der Hauptschuldige hier ist, wie oben erwähnt, der Aufruf von range (). Es wird eine Liste mit 15 Millionen Mitgliedern erstellen, die 200 MB Ihres Speichers auffressen wird, und mit 15 Prozessen sind das 3 GB.

Aber lesen Sie auch nicht die ganze 15MB Datei in data (), lesen Sie bitweise aus der Antwort. Wenn Sie diese 15 MB in eine Variable stecken, verbrauchen Sie 15 MB mehr als bitweise aus der Antwort lesen.

Vielleicht möchten Sie einfach nur Daten extrahieren, bis Sie bei indata ausgehen, und die Anzahl der extrahierten Daten mit dem vergleichen, was die ersten Bytes sagten. Dann brauchst du weder range () noch xrange (). Scheint mir pythonischer. :)

    
Lennart Regebro 02.11.2009 08:31
quelle
6

Verwenden Sie xrange () anstelle von range (), ich glaube, dass xrange ein Generator ist, während range () die gesamte Liste erweitert.

Ich würde sagen, entweder lese nicht die ganze Datei in den Speicher, oder halte nicht die ganze entpackte Struktur im Speicher.

Momentan behalten Sie beide im Speicher, gleichzeitig wird das ziemlich groß. Sie haben also mindestens zwei Kopien Ihrer Daten im Speicher sowie einige Metadaten.

Auch die letzte Zeile

%Vor%

Kann eigentlich bedeuten, dass Sie vorübergehend eine dritte Kopie im Speicher haben (eine große Zeichenfolge mit der gesamten Ausgabedatei).

Also würde ich sagen, dass Sie es auf ziemlich ineffiziente Weise tun, indem Sie die gesamte Eingabedatei, die gesamte Ausgabedatei und eine große Menge an Zwischendaten gleichzeitig im Speicher halten.

Mit dem Generator zu analysieren ist eine ziemlich gute Idee. Erwägen Sie, jeden Datensatz nach dem Generieren zu schreiben (er kann dann verworfen und der Speicher erneut verwendet werden), oder, falls dies zu viele Schreibanforderungen verursacht, sollten Sie ihn in z. B. 100 Zeilen auf einmal ablegen.

Ebenso könnte das Lesen der Antwort in Blöcken erfolgen. Da es sich um feste Datensätze handelt, sollte dies relativ einfach sein.

    
MarkR 02.11.2009 07:56
quelle
5

Die letzte Zeile sollte sicher f.close() sein? Diese nachlaufenden Parens sind irgendwie wichtig.

    
Caleb Hattingh 02.11.2009 07:21
quelle
2

Sie können dieses Programm effizienter machen, indem Sie nicht alle 15 MB von der TCP-Verbindung lesen, sondern jede Zeile so verarbeiten, wie sie gelesen wird. Dadurch werden die Remote-Server natürlich auf Sie warten, aber das ist in Ordnung.

Python ist einfach nicht sehr speichereffizient. Dafür wurde es nicht gebaut.

    
vy32 02.11.2009 06:25
quelle
2

Sie könnten mehr von Ihrer Arbeit in kompiliertem C-Code tun, wenn Sie dies in ein Listenverständnis umwandeln:

%Vor%

zu:

%Vor%

Das ist eigentlich etwas anders als Ihr ursprünglicher Code. In Ihrer Version gibt GetData ein 3-Tupel zurück, das in Items zurückkommt. Dann iterieren Sie über dieses Triplet und fügen ";" (Join) für jedes Element hinzu. Dies bedeutet, dass Sie für jedes Triplet, das von GetData gelesen wird, 3 Einträge zu den Daten hinzugefügt bekommen, jeweils ';'. Wenn die Elemente nur Zeichenfolgen sind, gibt ';' .join Ihnen eine Zeichenfolge mit jedem anderen Zeichen a ';' zurück. - das ist ";". join ("ABC") gibt "A; B; C" zurück. Ich denke, was Sie eigentlich wollten war, dass jedes Triplet in die Datenliste als die drei Werte des Triplets gespeichert wurde, getrennt durch Semikolons. Das ist, was meine Version erzeugt.

Dies kann auch etwas bei Ihrem ursprünglichen Speicherproblem helfen, da Sie nicht mehr so ​​viele Python-Werte erstellen. Denken Sie daran, dass eine Variable in Python viel mehr Aufwand hat als eine in einer Sprache wie C. Da jeder Wert selbst ein Objekt ist und den Overhead jeder Namensreferenz zu diesem Objekt hinzufügt, können Sie die theoretischer Lagerbedarf mehrfach. In Ihrem Fall, wenn Sie 15Mb X 15 = 225Mb lesen und der Overhead jedes Artikels jedes Triple als String-Eintrag in Ihrer Datenliste gespeichert wird, könnte dies schnell zu Ihrer beobachteten Größe von 2 GB werden. Zumindest enthält meine Version Ihrer Datenliste nur 1/3 der Einträge, außerdem werden die einzelnen Elementreferenzen übersprungen, und die Iteration erfolgt in kompiliertem Code.

    
PaulMcG 02.11.2009 07:43
quelle
2

Es gibt zwei offensichtliche Orte, an denen Sie große Datenobjekte im Speicher behalten ( data Variable in GetData() und data in MyThread.run() - diese beiden benötigen ungefähr 500Mb) und wahrscheinlich gibt es andere Stellen im übersprungenen Code. Es gibt beide einfach, Speicher effizient zu machen. Verwenden Sie response.read(4) , anstatt die gesamte Antwort auf einmal zu lesen, und machen Sie das im Code hinter UNPACK FIXED LENGTH OF BINARY DATA HERE . Ändern Sie data.append(...) in MyThread.run() in

%Vor%

Diese Änderungen sparen viel Speicherplatz.

    
Denis Otkidach 02.11.2009 09:13
quelle
1

Stellen Sie sicher, dass Sie die Threads nach dem Stoppen löschen. (mit del )

    
Tarnay Kálmán 02.11.2009 09:22
quelle