Seite 1 von 2
Stetig steigender Hauptspeicherbedarf
Verfasst: Donnerstag 24. Oktober 2019, 10:07
von BastiL
Hallo zusammen,
ich habe Probleme mit einem Programmsegment, das - aus mir unerklärlichen Gründen - stetig steigenden Hauptspeicher braucht:
Code: Alles auswählen
import gc
for i in range(0,100):
res = {}
for j in range(0,2000000):
res.update{j: result[j])
print(res)
# del res
# gc.collect()
results enthält die zugehärigen Daten. Mein Problem ist, dass das dictionary "res" mit jedem Durchlauf der äußeren Schleife mehr Platz im Hauptspeicher braucht (im Bereich 500MB, final bräuchte ich also rund 50 GB Hauptspeicher). Dabei wird res nach dem Schreiben der Daten (hier symbolisiert durch print(res)) nicht mehr gebraucht und kann gelöscht und beim nächsten Schleifendurchlauf überschrieben werden. Ich habe die beiden auskommentierten Lösungen ausprobiert, aber ohne Erfolg. Wie kann ich den Speicher freigeben oder dem Problem weiter auf die Schliche kommen. Danke.
Re: Stetig steigender Hauptspeicherbedarf
Verfasst: Donnerstag 24. Oktober 2019, 11:04
von __blackjack__
@BastiL: Das ist kein lauffähiger Code. Ich weiss auch nicht was die syntaktisch falsche Zeile eigentlich machen soll‽ Kann es sein, dass das eigentlich so aussehen sollte:
Code: Alles auswählen
for _ in range(100):
res = {j: result[j] for j in range(2_000_000)}
print(res)
Wie kommst Du denn darauf das diese Zeilen Schuld am Speicherverbrauch sind? Ich sehe da nämlich keinen Grund für.
Warum muss `res` ein Wörterbuch sein? Das macht wenig Sinn, denn ein Wörterbuch mit aufsteigenden ganzen Zahlen die bei 0 anfangen ist ja eigentlich eine Liste, nur eben in einer komplexeren Datenstruktur gespeichert.
``del`` bringt für so etwas nichts, denn ``del name`` löscht den *Namen*, nicht das Objekt. Das *kann* eine Folge davon sein, aber natürlich nur wenn das Objekt nicht noch von irgendwo anders her referenziert wird. Dann wäre es ja fatal wenn man mit ``del`` das Objekt löschen könnte, denn was sollte denn dann passieren wenn man über die andere Referenz auf das Objekt zugreift‽
Re: Stetig steigender Hauptspeicherbedarf
Verfasst: Donnerstag 24. Oktober 2019, 12:21
von Sirius3
Was ist `result` für ein Objekt? Da es quasi das selbe Zugriffsmuster auf die Daten erlaubt, wie `res` ist `res` überflüssig. (Nebenbei bemerkt: `update` benutzt man nicht, wenn man genau einen Schlüssel einem Wörterbuch hinzufügen will)
Re: Stetig steigender Hauptspeicherbedarf
Verfasst: Donnerstag 24. Oktober 2019, 13:02
von __blackjack__
Die Frage ist was mit `res` gemacht wird. Wenn das auch verändert wird, ist es nicht überflüssig. Falls nur lesend (bezüglich der direkten Ebene von `res`) zugegriffen werden soll und `len()` sowie Schlüsselwerte ausserhalb von 0 bis 1.999.999 das entsprechende Ergebnis liefern sollen, kann man sich eine entsprechende Wrapper-Klasse schreiben, so das man nicht tatsächlich eine neue Datenstruktur mit 2 Mio. Einträgen erstellen muss.
Re: Stetig steigender Hauptspeicherbedarf
Verfasst: Freitag 25. Oktober 2019, 09:09
von BastiL
Sirius3 hat geschrieben: ↑Donnerstag 24. Oktober 2019, 12:21
Was ist `result` für ein Objekt? Da es quasi das selbe Zugriffsmuster auf die Daten erlaubt, wie `res` ist `res` überflüssig. (Nebenbei bemerkt: `update` benutzt man nicht, wenn man genau einen Schlüssel einem Wörterbuch hinzufügen will)
Ok, was benutzt man statt update in diesem Fall? Einfach den Wert ergänzen wahrscheinlich ...
results ist eine Schnittstelle, zu einer kommerziellen Software, die neben mehrere Parameter erfordert und teils noch verrechnet werden muss.
Re: Stetig steigender Hauptspeicherbedarf
Verfasst: Freitag 25. Oktober 2019, 09:10
von BastiL
BastiL hat geschrieben: ↑Donnerstag 24. Oktober 2019, 10:07
Hallo zusammen,
ich habe Probleme mit einem Programmsegment, das - aus mir unerklärlichen Gründen - stetig steigenden Hauptspeicher braucht:
Code: Alles auswählen
import gc
for i in range(0,100):
res = {}
for j in range(0,2000000):
res.update({j: result[j]})
print(res)
# del res
# gc.collect()
results enthält die zugehärigen Daten. Mein Problem ist, dass das dictionary "res" mit jedem Durchlauf der äußeren Schleife mehr Platz im Hauptspeicher braucht (im Bereich 500MB, final bräuchte ich also rund 50 GB Hauptspeicher). Dabei wird res nach dem Schreiben der Daten (hier symbolisiert durch print(res)) nicht mehr gebraucht und kann gelöscht und beim nächsten Schleifendurchlauf überschrieben werden. Ich habe die beiden auskommentierten Lösungen ausprobiert, aber ohne Erfolg. Wie kann ich den Speicher freigeben oder dem Problem weiter auf die Schliche kommen. Danke.
Code korrigiert.
Re: Stetig steigender Hauptspeicherbedarf
Verfasst: Freitag 25. Oktober 2019, 09:20
von BastiL
__blackjack__ hat geschrieben: ↑Donnerstag 24. Oktober 2019, 11:04
@BastiL: Das ist kein lauffähiger Code. Ich weiss auch nicht was die syntaktisch falsche Zeile eigentlich machen soll‽ Kann es sein, dass das eigentlich so aussehen sollte:
Code: Alles auswählen
for i in range(0,100):
res = {}
for j in range(0,2000000):
res.update({j: result[j]})
print(res)
Habe es hier nochmal korrigiert.
Wie kommst Du denn darauf das diese Zeilen Schuld am Speicherverbrauch sind? Ich sehe da nämlich keinen Grund für.
Sagt mir tracemalloc.
Warum muss `res` ein Wörterbuch sein? Das macht wenig Sinn, denn ein Wörterbuch mit aufsteigenden ganzen Zahlen die bei 0 anfangen ist ja eigentlich eine Liste, nur eben in einer komplexeren Datenstruktur gespeichert.
Die keys sind im Vollprogramm nicht zwangsweise aufsteigende Zahlen. Das war nur in diesem Beispiel-Listenkonstrukt so.
Re: Stetig steigender Hauptspeicherbedarf
Verfasst: Freitag 25. Oktober 2019, 09:30
von __blackjack__
@BastiL: Wie hast Du `tracemalloc` eingesetzt? Bau das doch mal in Dein Beispiel ein. So dass das auch das Verhalten zeigt was Du in Deinem Originalcode siehst. Falls man das im Beispiel dann nämlich nicht sieht, dann nützt das Beispiel nichts.
Code: Alles auswählen
# falsch
res.update({j: result[j]})
# richtig
res[j] = result[j]
Und für die gesamte Schleife aber eher:
Code: Alles auswählen
res = {}
res.update((j, result[j]) for j in range(2_000_000))``
# oder
res = {j: result[j] for j in range(2_000_000)}
Re: Stetig steigender Hauptspeicherbedarf
Verfasst: Freitag 25. Oktober 2019, 09:39
von sparrow
@BastiL Und reduziert man dein Beispielprogramm so, dass man es testweise laufen lassen kann, kommt es nicht zu dem beschriebenen Problem. Selbst ohne
List Comprehension.
Code: Alles auswählen
while True:
data = {}
for j in range(0, 2_000_000):
data[j] = j
Re: Stetig steigender Hauptspeicherbedarf
Verfasst: Freitag 25. Oktober 2019, 09:52
von Sirius3
BastiL hat geschrieben: ↑Freitag 25. Oktober 2019, 09:09
results ist eine Schnittstelle, zu einer kommerziellen Software,
Hier müßtest Du noch etwas ausführlicher werden. Es handelt sich also nicht um eine Standard-Python-Struktur?
Dann wäre es sehr wahrscheinlich, dass das externe Modul irgend ein Speicherproblem hat, also nicht sauber aufräumt, wenn Python Objekte freigibt.
Aber ohne die Information, was das für ein kommerzielles Programm ist, kann man hier nicht weiter helfen.
Re: Stetig steigender Hauptspeicherbedarf
Verfasst: Montag 28. Oktober 2019, 12:07
von BastiL
Sirius3 hat geschrieben: ↑Freitag 25. Oktober 2019, 09:52
Hier müßtest Du noch etwas ausführlicher werden. Es handelt sich also nicht um eine Standard-Python-Struktur?
Dann wäre es sehr wahrscheinlich, dass das externe Modul irgend ein Speicherproblem hat, also nicht sauber aufräumt, wenn Python Objekte freigibt.
Aber ohne die Information, was das für ein kommerzielles Programm ist, kann man hier nicht weiter helfen.
Korrekt es ist keine Standard-Python-Struktur. Die Values des dictionaries "res" melden sich mit dem Typ:
Hier wird wohl der Hund begraben liegen. Ich muss mir Gedanken machen, ob ich das kopieren in die dictionary-Struktur nicht komplett vermeiden kann. Wird aufwändiger aber könnte klappen.
Re: Stetig steigender Hauptspeicherbedarf
Verfasst: Montag 28. Oktober 2019, 12:22
von __blackjack__
@BastiL: Das sollte dann aber kein Problem sein, denn das scheinen ja ganz normale Python-`float`-Objekte zu sein:
Re: Stetig steigender Hauptspeicherbedarf
Verfasst: Montag 28. Oktober 2019, 13:13
von sparrow
Und das Problem ist auch nicht die Zieldatenstruktur.
Re: Stetig steigender Hauptspeicherbedarf
Verfasst: Montag 28. Oktober 2019, 14:00
von Sirius3
@BastiL: ok, der Index-Zugriff auf `result` liefert normale float-Werte. Aber was für einen Typ hat `result`?
Re: Stetig steigender Hauptspeicherbedarf
Verfasst: Dienstag 29. Oktober 2019, 08:12
von BastiL
Sirius3 hat geschrieben: ↑Montag 28. Oktober 2019, 14:00
@BastiL: ok, der Index-Zugriff auf `result` liefert normale float-Werte. Aber was für einen Typ hat `result`?
Eben den oben genannten. Ich habe die type-Abfrage direkt auf "results" gemacht, nicht auf den value des dicts "res".
Komplett auf ein Sammeln zu verzichten will ich nicht so richtig. Die Ergebnisse werden über eine allgemein gehaltene Klasse ausgegeben (symbolisiert durch "print") die die Ergebnisse ausgabefertig über das dict "res" bekommt. Die DAtenbearbeitung möchte ich eigentlich nicht in die Ausgabeklasse umziehen und brauche deshalb einen Zwischenspeicher.
Re: Stetig steigender Hauptspeicherbedarf
Verfasst: Dienstag 29. Oktober 2019, 08:23
von BastiL
Ok, ich muss noch nachreichen, wie ich tracemalloc implementiert habe:
Code: Alles auswählen
import tracemalloc
CODE VON OBEN
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')
print('TOP10')
for stats in top_stats[:10]
print(stat)
Re: Stetig steigender Hauptspeicherbedarf
Verfasst: Dienstag 29. Oktober 2019, 08:23
von sparrow
Ein
in dem Code aus deinem ersten Post sagt, dass es ein float ist? Das halte ich für ausgeschlossen.
Du scheinst da noch immer zu denken, dass das Datenstruktur - also das dict -, in der du die Werte zwischenspeicherst das Problem ist. Dem ist nicht so.
Edit:
BastiL hat geschrieben: ↑Dienstag 29. Oktober 2019, 08:12Die Ergebnisse werden über eine allgemein gehaltene Klasse ausgegeben (symbolisiert durch "print") die die Ergebnisse ausgabefertig über das dict "res" bekommt.
Etwas durch etwas anderes zu "symbolisieren" ist übrigens selten eine gute Idee. Etwas mit "print" auszugeben verhält sich ganz anders als wenn die Daten aus "res" (übrigens ein schlechter Name) zum Beispiel an eine andere Datenstruktur angehängt werden. Dann würde natürlich weiterhin eine Referenz auf das Objekt bestehen und es würde niemals abgeräumt werden.
Bist du dir sicher, dass bei deinem Code aus dem ersten Post das Problem auch auftritt oder nimmst du nur an, dass es auch auftreten würde? Und zwar genau so, wie er da steht.
Re: Stetig steigender Hauptspeicherbedarf
Verfasst: Dienstag 29. Oktober 2019, 09:25
von Sirius3
Wir kommen nicht weiter, wenn Du nicht endlich schreibst, was denn `results` für ein Typ ist. Zeigt doch mal den realen kompletten Code. Beschreibe exakt, was Du denn mit den Daten anfangen willst, denn das ungefähre verwirrt nur.
Re: Stetig steigender Hauptspeicherbedarf
Verfasst: Mittwoch 30. Oktober 2019, 08:28
von BastiL
Hallo zusammen,
ich bin mir natürlich nicht sicher, das sich mit meinem Segment das Problem nachstellen lässt. Ich kann gerne den realen Code-Auszug posten, nur wird den niemand ohne das kommerzielle Programm nachvollziehen können. Daher dachte ich, ich vereinfache das Problem. Hier die originale Funktion aus dem Code:
Code: Alles auswählen
def write_results():
for i, nscalar in enumerate(nodescalars):
res = {} # dict {node-id : result}
for node in list(node_ids.keys()):
res[node] = t16file.node_scalar(node_ids_mentat[node], i)
# print(type(t16file.node_scalar(node_ids_mentat[node], i)))
if write_ascii:
ensfile.write_scalar_node_variable_time_step(nscalar,
res,t16file.increment,compnodes)
else:
ensfile.write_scalar_node_var_bina_time_step(nscalar,
res,t16file.increment,compnodes)
del res
gc.collect()
Folgende Variableninfo:
nodescalars: Eine Liste mit Strings
node_ids, node_ids_mentat: Dictionaries mit Knotennummern als Schlüsselwerte und Listen mit x,y,z-Koordinaten als Values
t16file: Instanz der Klasse "py_post", eine Python-Schnittstelle der kommerziellen FE-Software MSC.MARC
ensfile: Instanz einer selbst geschriebenen Klasse zur Ausgabe der Daten
Wenn ich den Kommentar vor print entferne um den Typ von "results" (in Wahrheit eben t16file.node_scalar(node_ids_mentat[node], i)) zu erfahren bekomme ich:
Der Gesamtcode hat 1000+ Zeilen. Falls weitere Segmente gebraucht werden einfach Bescheid sagen. tracemalloc sagt mir, dass die Zeile:
Code: Alles auswählen
res[node] = t16file.node_scalar(node_ids_mentat[node], i)
mit jedem Durchlauf der äußeren Schleife mehr Hauptspeicher allokiert.
Re: Stetig steigender Hauptspeicherbedarf
Verfasst: Mittwoch 30. Oktober 2019, 08:51
von sparrow
Das del und den Aufruf von gc.collect() kannst du dir sparen.
Wo kommen denn die ganzen Variablennamen magisch her? Der Funktion wird ja einfach gar nichts übergeben. Im Augenblick würde ich sagen, das Problem ist eher deine Klasse, die sich hinter "ensfile" verbirgt. Wo landen denn die Daten, die du dort hinein kippst? Denn du sammelst sie ja ein und gibst sie dorthin weiter. Und wenn sie dort weiter gesammelt werden, dann wird es eben auch nicht weniger.
Dadurch dass aber alle Variablen auch noch global sind, ist das für dich ohnehin schwierig bis unmöglich zu debuggen.
Mit deinem "Beispielcode" hat das hier übrigens gar nichts mehr zu tun. Außer einer Schleife sehe ich nichts was gleich ist. Vor allem nicht die dahinterliegenden Datenstrukturen und Objekte.