Speicherverwaltung, Garbage Collector

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
frixhax
User
Beiträge: 39
Registriert: Donnerstag 21. April 2011, 14:06

Hallo zusammen!
Ich habe ein Programm, welches sehr große Tabellen aus einer Reihe von Dateien einliest, darauf Rechenoperationen durchführt und dann in neue Dateien schreibt. Dabei wird über alle Dateien iteriert und jeweils derselbe code ausgeführt. Ein extrem minimiertes Beispiel wäre so etwas wie

Code: Alles auswählen

import glob
import numpy

file_list = glob.glob('datei*.dat')
for file in file_list:
    my_file = open(file)
    my_lines = my_file.readlines()
    my_file.close()
    my_new_array = []
    for line in my_lines:
        my_new_array.append(line.split())
    my_new_array = numpy.asarray(my_new_array) * 5.
    numpy.savetxt('output_' + str(i), my_new_array)
Klar, es gibt andere Methoden, um Dateien einzulesen. Das soll jetzt nur exemplarisch sein. Das Problem ist nun, dass der Speicher, den my_lines und my_new_array belegen, nicht freigegeben wird. Wenn ich also die zweite Datei einlese, verbleiben die Listen/Arrays der ersten Datei im RAM. Ich hätte gedacht, dass der Garbage Collector den Speicher freigibt, tut er aber nicht. So wird mein Speicher mit jeder Iteration immer voller und voller, obwohl das gar nicht nötig ist. Wie kann ich das verhindern?
Viele Dank und viele Grüße
Benutzeravatar
Sr4l
User
Beiträge: 1091
Registriert: Donnerstag 28. Dezember 2006, 20:02
Wohnort: Kassel
Kontaktdaten:

mohack hat geschrieben:

Code: Alles auswählen

import glob
import numpy

file_list = glob.glob('datei*.dat')
for file in file_list:
    my_file = open(file)
    my_lines = my_file.readlines()
    my_file.close()
    my_new_array = []
    for line in my_lines:
        my_new_array.append(line.split())
    my_new_array = numpy.asarray(my_new_array) * 5.
    numpy.savetxt('output_' + str(i), my_new_array)
Da my_new_array auch außerhalb der for-Schleife gültig ist ziehst du den Inhalt in den neuen Schleifendurchlauf mit und gibst ihn erst zum löschen nachdem my_lines eingelesen wurde. Du hast also immer zeitgleich 2 Dateien im Speicher, an zwei Variablen gebunden

Die zweite schlimme Sache für den Arbeitsspeicher ist: my_lines = my_file.readlines(), das ist Blödsinn weil du danach nochmal darüber itterierst kannst du auch sowas machen (beachte das fehlende `s` an readline:

Code: Alles auswählen

    wilth open(file) as my_file:
        my_new_array = []
        for line in my_file.readline():
            my_new_array.append(line.split())
Einen Fehler der alle Dateien im Speicher hält gibt es bei dir nicht, allerdings fehlt auch das i für die Namensgebung in deinem Code.
Benutzeravatar
/me
User
Beiträge: 3555
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

Sr4l hat geschrieben:

Code: Alles auswählen

        for line in my_file.readline():
            my_new_array.append(line.split())
Das readline() ist hyperfluid. Du kannst direkt über das Dateiobjekt iterieren.

Code: Alles auswählen

        for line in my_file:
            my_new_array.append(line.split())
Benutzeravatar
frixhax
User
Beiträge: 39
Registriert: Donnerstag 21. April 2011, 14:06

Danke, das mit dem Einlesen werde ich ändern. Trotzdem werden alle einmal angelegten arrays anscheinend im Speicher gelassen und bis zum Ende des kompletten Programms nicht wieder freigegeben, was irgendwann im Systemcrash resultiert. Kann ich das verhindern?
webspider
User
Beiträge: 485
Registriert: Sonntag 19. Juni 2011, 13:41

Du musst das Einlesen ändern, damit sich das Verhalten des Programms hinsichtlich Arbeitsspeicher bessert. Der Garbage-Collector räumt nur auf sobald keine Referenzen mehr auf ein Objekt zeigen.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Mach dir das Leben doch nicht so schwer und nutze einfach `numpy.loadtxt()`. Dieser Funktion kannst du einen Dateinamen und den Typ der in der Datei enthaltenen Daten übergeben und dann wird der Rest Numpy-intern erledigt. Ein möglicher Aufruf wäre also:

Code: Alles auswählen

mein_array = numpy.loadtxt('test.dat', float)
Wobei die Angabe des Datentyps auch weggelassen werden kann. In diesem Fall nimmt Numpy immer an, dass es sich um Floats handelt.

Dies wird sich mit Sicherheit auch positiv auf den Speicherverbrauch deines Programms auswirken.
Benutzeravatar
frixhax
User
Beiträge: 39
Registriert: Donnerstag 21. April 2011, 14:06

Klar, loadtxt würde ich nutzen, wenn es sich in den Tabellen nur um Zahlen handeln würde. Das ist aber nicht der Fall (das daoben sollte nur ein Minimalbeispiel sein). Alternativ müsste ich dann also asciitable nutzen, das ist aber im Vergleich unsagbar langsam.

Kann man nicht irgendwie erreichen, dass zum Ende eines Schleifendurchlaufs die Referenzen freigeben werden? Der Garbage Collector müsste ja eigentlich selbst merken, dass in der nächsten Iteration das Objekt oder die Variable neu angelegt wird und dass daher der alte Inhalt nicht mehr geschützt im Speicher verbleiben muss. Macht er aber anscheinend nicht. Kann man ihm sagen, dass er - was den Speicher angeht - jede Iternation quasi wieder bei null beginnen soll?
BlackJack

@mohack: Bei dem gezeigten Quelltext werden nicht alle Daten von allen Dateien im Speicher gehalten. Wenn Du Quelltext zeigst der ein Problem aufzeigen soll, dann stell bitte sicher, dass der Quelltext den Du zeigst, dieses Problem auch tatsächlich hat. So dass man das nachvollziehen kann.

Also in diesem Fall Quelltext der lauffähig ist (`i`), Angaben darüber wie die Dateien ungefähr aussehen und von wie vielen Werten wir reden, damit man sich das nachvollziehen kann (im Idealfall vielleicht ein kleines Skript angeben mit dem sich Dummy-Daten erzeugen lassen), und dann eventuell noch eine Angabe wie Du das mit dem Speicher überprüfst und was für Werte dabei heraus kommen.

Grundsätzlich wurde ja schon angesprochen, dass Namen auf Modulebene gebunden sind, solange das Programm läuft. Funktionen können hier praktisch sein um den Gültigkeitsbereich und damit auch die Lebensdauer von Namen sinnvoll zu begrenzen. Man sammelt dann nicht unnötig Zwischenergebnisse auf (modul)globaler Ebene an. Ausserdem kann man dann flexibler testen was einzelne Schritte tun, Alternativen ausprobieren und vergleichen, und vielleicht sogar Teile wiederverwenden.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@mohack:

Wenn man Python sagen will, dass eine große Datenstruktur jetzt freigegeben werden kann, dann lässt sich dies beispielsweise mittels `del` lösen. Du müsstest also jedes Mal `del meine_liste` am Ende der Schleife sagen. Andere Möglichkeiten wären `del meine_liste[:]` (leert die Liste aber löscht sie nicht, vgl. `.clear()` aus anderen Programmiersprachen) oder `meine_liste = None` (falls man den Namen an sich - wozu auch immer - erhalten möchte).

Letztendlich ist das alles aber IMHO eine Art von Code Smell und du solltest demzufolge besser das tun, was BlackJack vorgeschlagen hat: Strukturiere den Code in abgeschlossene Namensräume (z.B. mithilfe von Funktionen), damit sich Python automatisch um die Referenzverwaltung kümmern kann. In guten Programmen braucht man `del` eigentlich niemals... ;)
Antworten