Stetig steigender Hauptspeicherbedarf

mit matplotlib, NumPy, pandas, SciPy, SymPy und weiteren mathematischen Programmbibliotheken.
BastiL
User
Beiträge: 135
Registriert: Montag 7. Juli 2008, 20:22

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.
Benutzeravatar
__blackjack__
User
Beiträge: 13122
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@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‽
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Sirius3
User
Beiträge: 17760
Registriert: Sonntag 21. Oktober 2012, 17:20

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)
Benutzeravatar
__blackjack__
User
Beiträge: 13122
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

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.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
BastiL
User
Beiträge: 135
Registriert: Montag 7. Juli 2008, 20:22

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.
Zuletzt geändert von BastiL am Freitag 25. Oktober 2019, 09:21, insgesamt 1-mal geändert.
BastiL
User
Beiträge: 135
Registriert: Montag 7. Juli 2008, 20:22

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.
BastiL
User
Beiträge: 135
Registriert: Montag 7. Juli 2008, 20:22

__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.
Benutzeravatar
__blackjack__
User
Beiträge: 13122
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@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)} 
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
sparrow
User
Beiträge: 4198
Registriert: Freitag 17. April 2009, 10:28

@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
Sirius3
User
Beiträge: 17760
Registriert: Sonntag 21. Oktober 2012, 17:20

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.
BastiL
User
Beiträge: 135
Registriert: Montag 7. Juli 2008, 20:22

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:

Code: Alles auswählen

<class 'float'>
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.
Benutzeravatar
__blackjack__
User
Beiträge: 13122
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@BastiL: Das sollte dann aber kein Problem sein, denn das scheinen ja ganz normale Python-`float`-Objekte zu sein:

Code: Alles auswählen

In [25]: print(type(42.23))                                                     
<class 'float'>
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
sparrow
User
Beiträge: 4198
Registriert: Freitag 17. April 2009, 10:28

Und das Problem ist auch nicht die Zieldatenstruktur.
Sirius3
User
Beiträge: 17760
Registriert: Sonntag 21. Oktober 2012, 17:20

@BastiL: ok, der Index-Zugriff auf `result` liefert normale float-Werte. Aber was für einen Typ hat `result`?
BastiL
User
Beiträge: 135
Registriert: Montag 7. Juli 2008, 20:22

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.
BastiL
User
Beiträge: 135
Registriert: Montag 7. Juli 2008, 20:22

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)
Benutzeravatar
sparrow
User
Beiträge: 4198
Registriert: Freitag 17. April 2009, 10:28

Ein

Code: Alles auswählen

type(result)
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.
Sirius3
User
Beiträge: 17760
Registriert: Sonntag 21. Oktober 2012, 17:20

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.
BastiL
User
Beiträge: 135
Registriert: Montag 7. Juli 2008, 20:22

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:

Code: Alles auswählen

<class 'float'>
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.
Benutzeravatar
sparrow
User
Beiträge: 4198
Registriert: Freitag 17. April 2009, 10:28

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.
Antworten