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

sparrow hat geschrieben: Mittwoch 30. Oktober 2019, 08:51 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.
Ok, die entsprechende Methode von "ensfile" reiche ich nach. Das sol laber eine Export-Funktion sein, die die Daten schreibt und anschließend werden diese Daten nicht mehr gebraucht.
Letztlich brauche ich die globalen Variablen den ganzen Programmdurchlauf über. Das eigentliche Problem scheint ja "res" zu sein, das ist eine lokale Variable.
Zuletzt geändert von BastiL am Mittwoch 30. Oktober 2019, 09:15, insgesamt 1-mal geändert.
Benutzeravatar
sparrow
User
Beiträge: 4535
Registriert: Freitag 17. April 2009, 10:28

Nee, "res" ist nicht das Problem. Und das habe ich schon sehr oft hier geschrieben. Du hängst dich an der falschen Sache auf.

Globale Variablen braucht man nie. Funktionen bekommen das, was sie brauchen, als Parameter und geben ein Ergebnis (mittels return) zurück.
Dann kann man Code debuggen.
Natürlich kann man das auch nicht tun und denken, dass man es besser kann. Das führt dann halt unkontrolliertem Verhalten, dem man nicht auf die Spur kommt.
BastiL
User
Beiträge: 135
Registriert: Montag 7. Juli 2008, 20:22

Ich bin kein Programmierexperte, deswegen würde ich nie behaupten, dass ich es besser kann. Eher das ich es nicht besser weiss.
Letztlich ist es so, dass der Großteil der Arbeit in diesem Fall im Hauptprogramm in globale Variablen gemacht wird. Die gezeigte Funktion greift auf diese Variablen zu, das ist richtig. Ich könnte die globalen Variablen aus dem Hauptprogramm auch an die Funktion übergeben, die Frage ist nur was das hier ändern würde?
Grundsätzlich ist das Programm historisch gewachsen, gleiches gilt für die globalen Variablen. Damit bin ich seit geraumer Zeit selbst nicht mehr zufrieden, deshalb wäre das vielleicht ein guter Zeitpunkt für eine grundsätzliche Überarbeitung der Datenstruktur. Hier freue ich mich über Vorschläge, wie ich so etwas grundsätzlich konzipiere.
Benutzeravatar
__blackjack__
User
Beiträge: 14020
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@BastiL: Anmerkungen: Der `list`-Aufruf ist überflüssig, im Grunde auch der `keys()`-Aufruf.

Bei den Namen sind Abkürzungen und fehlende Unterstriche zwischen Worten.

Die Entscheidung welche Methode zum Schreiben verwendet wird kann man vor die Schleife ziehen.

Das Schleifenergebnis lässt sich wie schon früher angedeutet als „comprehension“ schreiben.

Die Funktion würde dann so aussehen:

Code: Alles auswählen

def write_results(
    node_scalars,
    node_ids,
    t16_file,
    node_ids_mentat,
    write_ascii,
    ens_file,
    comp_nodes,
):
    if write_ascii:
        write = ens_file.write_scalar_node_variable_time_step
    else:
        write = ens_file.write_scalar_node_var_bina_time_step

    for i, node_scalar in enumerate(node_scalars):
        node_id_to_result = {
            node_id: t16_file.node_scalar(node_ids_mentat[node_id], i)
            for node_id in node_ids
        }
        write(node_scalar, node_id_to_result, t16_file.increment, comp_nodes)
Globale Variablen braucht man nicht. Die einfachste und sinnvollste Art Programmiersprachen mit automatischer Speicherverwaltung dabei zu helfen sind ordentliche Funktionen zu schreiben — also keine globalen Variablen zu verwenden.
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
BastiL
User
Beiträge: 135
Registriert: Montag 7. Juli 2008, 20:22

Blackjack: Ok, die Funktion werde ich so umsetzen und berichten wie sich der Speicherbedarf damit entwickelt. Leuchtet mir alles ein. Einzig mit comprehensions stehe ich auf Kriegsfuss, weil ich den Code nicht so gut lesbar und verständlich finde. Welchen Vorteil bietet die comprehension hier?
Benutzeravatar
__blackjack__
User
Beiträge: 14020
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@BastiL: Naja, ein Vorteil von der „comprehension“-Syntax ist das er lesbarer und verständlicher ist. Man sieht sofort das der Zweck ist ein Wörterbuch zu erstellen, und nichts anderes. Und man hat dafür einen eigenen Namensraum, das heisst auch einen Namen ausserhalb des Ausdrucks weniger. `node_id` existiert nur in dem Ausdruck. Etwas effizienter ist es auch.

Am Speicherverbrauch dürfte das nichts ändern, denn es ist unwahrscheinlich das diese Funktion letztlich die entscheidende Stelle ist. Falls es `res` ist, dann wird der Speicher zwar in dieser Funktion belegt, aber in der Funktion selbst sehe ich keinen Grund warum der Speicher nicht auch wieder freigegeben wird. Das kann eigentlich nur passieren wenn die von dort aufgerufene(n) Schreibfunktion(en) Referenzen aufhebt.

Edit: `t16_file.node_scalar()` könnte übrigens auch der Übeltäter sein falls da Daten in dem Speicher geladen werden die dort vorher nicht waren und die dann aber nicht wieder freigegeben sondern beispielsweise gecached werden.
Zuletzt geändert von __blackjack__ am Mittwoch 30. Oktober 2019, 10:49, insgesamt 1-mal geändert.
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
Sirius3
User
Beiträge: 18260
Registriert: Sonntag 21. Oktober 2012, 17:20

Jetzt ergibt sich doch ein viel klareres Bild. Du hast also gar keinen Indexzugriff auf `results` sondern rufst eine Funktion auf.
Sowohl `list` als auch `keys` sind bei `list(node_ids.keys()):` überflüssig.
Das `del` hat wie schon geschrieben, keinen Einfluß und kann weg, ebenso das `collect`.
Die Funktion hat zu wenig Argumente und zu viele globale Variablen.

Um das Problem mal klarer zu haben:

Code: Alles auswählen

def write_results(nodescalars, node_ids_mentat, node_ids, t16file, ensfile, write_ascii, nscalar, compnodes):
    write = ensfile.write_scalar_node_variable_time_step if write_ascii else ensfile.write_scalar_node_var_bina_time_step
    for i, nscalar in enumerate(nodescalars):
        res = {
            node_id: t16file.node_scalar(node_ids_mentat[node_id], i)
            for node_id in node_ids
        }
        write(nscalar, res, t16file.increment, compnodes)
Nun gibt es zwei Stellen, wo Speicher angehäuft werden könnten:
`t16file.node_scalar` oder `ensfile.write_scalar_node_variable_time_step`. Um das zu testen, kannst Du einfach nur node_scalar aufrufen:

Code: Alles auswählen

def write_results(nodescalars, node_ids_mentat, node_ids, t16file, ensfile, write_ascii, nscalar, compnodes):
    for i, nscalar in enumerate(nodescalars):
        for node_id in node_ids:
            t16file.node_scalar(node_ids_mentat[node_id], i)
Wenn das den Speicher füllt, würde das bedeuten, dass dieses t16file nur bei Bedarf geladen wird und da Du mit der Zeit das ganze File brauchst, landet das mit der Zeit auch komplett im Speicher. Lösung wäre, das File bei jedem Loop neu zu öffnen und danach wieder zu schließen.

Liegt der Speicherverbrauch beim Speichern, dann mußt Du irgendwie dafür sorgen, dass die Daten tatsächlich geschrieben werden und nicht im Speicher bleiben. Da ich aber zu diesem ensfile keine Dokumentation gefunden habe, kann ich da nicht weiterhelfen.
BastiL
User
Beiträge: 135
Registriert: Montag 7. Juli 2008, 20:22

Sirius3 hat geschrieben: Mittwoch 30. Oktober 2019, 10:45 Nun gibt es zwei Stellen, wo Speicher angehäuft werden könnten:
`t16file.node_scalar` oder `ensfile.write_scalar_node_variable_time_step`.

Wenn das den Speicher füllt, würde das bedeuten, dass dieses t16file nur bei Bedarf geladen wird und da Du mit der Zeit das ganze File brauchst, landet das mit der Zeit auch komplett im Speicher. Lösung wäre, das File bei jedem Loop neu zu öffnen und danach wieder zu schließen.
Auf "t16file.nodescalar" ist mein Verdacht schon früh gefallen. Ich habe das damals durch Schließen und wieder Öffnen des FIles im Speicher getestet - ohne Erfolg. Bleibt wohl ensfile.write_scalar_node_variable_time_step:

Code: Alles auswählen

def write_scalar_node_variable_time_step(self, var_name, variable, time, parts):

    self.__scalar_var_file = open(self.directory + var_name + '%03i.scl'%time, 'w')
    self.__scalar_var_files.append(self.__scalar_var_file)

    print('time =', time, file=self.__scalar_var_file)
    for PartID in list(parts.keys()):
        print('part', file=self.__scalar_var_file)
        print('%10i'%PartID, file=self.__scalar_var_file)
        print('coordinates', file=self.__scalar_var_file)
        for node in sorted(parts[PartID]):
            print('%12.5f'%variable[node], file=self.__scalar_var_file)
    self.__scalar_var_file.close()
    return
Danke für euere Verbesserungsvorschläge für die Funktion, ich bin gerade dabei das umzusetzen.
Sirius3
User
Beiträge: 18260
Registriert: Sonntag 21. Oktober 2012, 17:20

Pfade stückelt man nicht mit + zusammen. Es macht auch selten Sinn, Fileobjekte als Attribut oder in Listen zu speichern, vor allem hier, wo Du das Fileobjekt am Ende der Funktion wieder schließt. Doppelte Unterstriche sind nur in Ausnahmefällen sinnvoll.

So wie es aussieht, hast Du ja dieses `write` selbst geschrieben, daran kann also der Speicherverbrauch nicht liegen. Wie hast Du denn t16file geöffnet und wieder geschlossen?

Code: Alles auswählen

def write_scalar_node_variable_time_step(self, var_name, variable, time, parts):
    with open(os.path.join(self.directory, '%s%03i.scl' % (var_name, time), 'w', encoding='ascii') as output:
        print('time =', time, file=output)
        for part_id, nodes in parts.items():
            print('part', file=output)
            print('%10i' % part_id, file=output)
            print('coordinates', file=output)
            for node in sorted(nodes):
                print('%12.5f' % variable[node], file=output)
BastiL
User
Beiträge: 135
Registriert: Montag 7. Juli 2008, 20:22

Sirius3 hat geschrieben: Mittwoch 30. Oktober 2019, 16:49 So wie es aussieht, hast Du ja dieses `write` selbst geschrieben, daran kann also der Speicherverbrauch nicht liegen. Wie hast Du denn t16file geöffnet und wieder geschlossen?
Ja ist selbst geschrieben basierend auf einer open source Bibliothek.
Für das Öffnen und Schließen des t16file sieht die Dokumentation der Schnittstelle jeweils genau eine Möglichkeit vor:

Code: Alles auswählen

t16file = py_post.post_open("Filename")
t16file.close()
Sirius3
User
Beiträge: 18260
Registriert: Sonntag 21. Oktober 2012, 17:20

Und wie hast Du die beiden Zeilen eingebaut?
Antworten