Arbeitsspeicher sparend coden

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.
Serpens66
User
Beiträge: 259
Registriert: Montag 15. Dezember 2014, 00:31

Hi :)


Worauf muss ich achten, wenn ich ein Python Skript unendlich lange in einem Loop laufen lassen will, damit nicht zuviel Arbeitsspeicher verbraucht wird? Ich habe nämlich das Problem, dass nach ca. 2-3 Tagen, mein gegebener Arbeitsspeicher von 512MB (ein Droplet auf digitalocean), voll ist und dadurch mein Skript beendet wird.

Wie immer ist mein Skript unglaublich lang und umfangreich und mein Erstlingswerk, also absolut mies geschrieben. Aber natürlich wird überall hier und da geflickt und verbessert, damit es iwann mal kein mieser Code mehr ist. Nun ist also das Arbeitsspeicher Problem dran.

Kurz zusammengefasst erstellt und verwendet mein Skript viele Dictionaries und Listen. Außerdem gibt es viele print()'s aus und schreibt die wichtigsten Meldungen in txt Dateien mithilfe von:

Code: Alles auswählen

with open('History.txt', 'a') as f :
                    f.write("%s" % text)
Erwähnenswert ist noch, dass ich mein Skript in Form eines Services laufen lassen.
D.h mit "service MeinSkript start" starte ich es und mit "journalctl -u MeinSkript --since..." frage ich einige prints ab.

Die Dauerschleife dauert ungefähr 10 sekunden. Die meisten dictionaries sind in self. Form und werden zu Beginn einmal leer erstellt. In jedem Durchlauf sollen sie wieder verwendet werden und einzelne Werte darin aktualisiert werden. dazu schreibe ich dann einfach sowas wie self.beispieldict[essen] = 3 usw. Natürlich verwende ich innerhalb von definitionen auch listen und dictionaries ohne self. , aber ich glaube die sollten kein Problem sein, da sie nach Beenden der definition ja wieder gelöscht werden, oder?

Nun frage ich mich, warum soviel Arbeitsspeicher verbraucht wird. Liegt es daran, dass es als service läuft (das journal beinhaltet irgendwann ja ziemlich viele prints)? Oder an den self. Variablen, obwohl diese ja wiederverwertet werden? Oder daran, dass das Schreiben in eine txt Datei nicht richtig gemacht wird?


Ich denke wir probieren es erstmal mit dem journal.
Wie könnte ich die im journal gespeicherten prints via skript löschen?


PS: Das Forum scheint seit ein paar Monaten ein neues Design zu haben, aber in allen alten Beiträgen werden dadurch die Python Codes nicht mehr schön angezeigt, da steht nur noch code=python file=Untitled.py, anstatt dass es richtig formatiert wird. Wird das noch irgendwann gefixt oder bleiben alte Beiträge nun unübersichtlich? (wurde sicher schonmal iwo angesprochen, aber es wundert mich schon sehr, dass es nach sovielen monaten noch immer nicht verbessert wurde O.ô )
Benutzeravatar
snafu
User
Beiträge: 6741
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Du müsstest uns schon konkreten Code zeigen. Gerne auch über ein externes Pastebin. So ganz allgemein würde ich sagen, dass du in der Schleife ständig neue Objekte anhängst und diese irgendwann den Speicher sprengen. Du müsstest insofern veraltete Objekte abräumen. Das geht entweder explizit mittels `del` oder – und viel häufiger anzutreffen – über eine Implementierung, die so programmiert ist, dass nicht mehr benötigte Objekte durch neue Objekte ersetzt werden. Wie gesagt: Konkreter kann man erst werden, wenn du den tatsächlich verwendeten Code zeigst.
Serpens66
User
Beiträge: 259
Registriert: Montag 15. Dezember 2014, 00:31

snafu hat geschrieben:Du müsstest uns schon konkreten Code zeigen. Gerne auch über ein externes Pastebin. So ganz allgemein würde ich sagen, dass du in der Schleife ständig neue Objekte anhängst und diese irgendwann den Speicher sprengen. Du müsstest insofern veraltete Objekte abräumen. Das geht entweder explizit mittels `del` oder – und viel häufiger anzutreffen – über eine Implementierung, die so programmiert ist, dass nicht mehr benötigte Objekte durch neue Objekte ersetzt werden. Wie gesagt: Konkreter kann man erst werden, wenn du den tatsächlich verwendeten Code zeigst.
mein skript ist 14k Zeilen lang, ich wüsste nicht, welcher Abschnitt speziell interessant sein würde.
Deswegen brauch ich erstmal die Erklärung allgemein, wie ich denn dafür sorge, dass nicht mehr benötigte Objekte ersetzt werden. Bzw. ein Beispiel wie es eben nicht aussehen darf ;)

Aber um uns viel Mühe zu ersparen, würde ich sagen gehen wir besser der Reihenfolge nach, was ich denke die hauptursache sein könnte.
Also wie sieht das mit dem journal aus ? Da sind ja dann viele viele prints drin, die ich abfragen kann. Das kann doch ein Arbeitspsiecher-fresser sein, oder? Wenn es via skript fürs erste zu kompliziert sein sollte, gibt es denn einen konsolen Befehl, den ich eintippen kann, um das journal eines services zu löschen? Dann sehe ich durch den "top" Befehl ja, ob der genutzte Arbeitsspeicher zurück geht, oder nicht :)

edit:
sehe gerade, dass im "top" befehl das journal explizit aufgelistet wird.
Kann mir jemand diese Ausgabe erklären? Es werden ja 468448 Bytes benutzt. Aber wo ergibt dieser Wert sich in der Tabelle?

Code: Alles auswählen

top - 00:40:01 up 6 days, 23:52,  1 user,  load average: 0.15, 0.11, 0.14
Tasks:  59 total,   1 running,  58 sleeping,   0 stopped,   0 zombie
%Cpu(s): 86.7 us,  0.7 sy,  0.0 ni, 11.3 id,  0.0 wa,  0.0 hi,  0.0 si,  1.3 st
KiB Mem:    506340 total,   468448 used,    37892 free,    90172 buffers
KiB Swap:        0 total,        0 used,        0 free.   157988 cached Mem

  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
15443 root      20   0  837800 171564   9128 S 87.8 33.9 123:11.86 python3.4
  157 root      20   0   37424   4800   2400 S  0.7  0.9  19:41.42 systemd-jou+
    1 root      20   0   28556   3940   2348 S  0.0  0.8   0:13.85 systemd
die Tabelle geht natürlich noch weiter, aber das sind jetzt die drei die oben stehen. Was muss ich addieren, um auf die 468448 zu kommen ?
Zuletzt geändert von Serpens66 am Dienstag 12. Januar 2016, 00:55, insgesamt 3-mal geändert.
Dav1d
User
Beiträge: 1437
Registriert: Donnerstag 30. Juli 2009, 12:03
Kontaktdaten:

Eine Datei 14k Zeilen :evil:

Kannst dir mit memory_profiler anschauen wo der memory herkommt. Wenn dir das nicht reicht gibts noch mehr Memory-Profiler, z.T. auch web basiert.
the more they change the more they stay the same
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@Serpens66: schreiben in eine Datei braucht keinen Arbeitsspeicher und der Journal-Dienst ist nur für die Verteilung der Daten da und, da er vom Anbieter stammt, hat auch nur mit sehr geringer Wahrscheinlichkeit ein Speicherleck.

Der erste Schritt um das Problem zu lösen, ist, die 14k Datei in kleine logische Einheiten aufzuspalten. Wenn es sich immer um einen unabhängigen Durchlauf handelt, sorge dafür, dass Du keine Daten überschreibst, sondern jedes Mal neue Objekte erzeugst. Dictionaries mit leeren Einträgen zu erzeugen, ist auch eher ein Zeichen dafür, dass man nicht pythonisch programmiert.
BlackJack

@Serpens66: Wenn man sauber programmiert ist das in der Regel schon der erste Schritt speicherschonend zu programmieren, weil dann kleine übersichtliche Teilaufgaben jeweils in einer eigenen Funktion erledigt werden, und nur lokal für diese Aufgabe benötigte Objekte auch nur so lange Speicher belegen müssen, wie die Funktion abläuft.

Ansonsten schliesse ich mich Sirius3 an: Wenn es geht, keine Datenstrukturen wiederverwenden die man wegwerfen könnte, denn dann muss man sich selber darum kümmern, dass sich da nicht irgendwo doch immer mehr Daten sammeln, weil man irgendwo vergessen hat welche explizit aus einer Datenstruktur zu entfernen. Wenn man alte Datenstrukturen verwirft und mit neuen Objekten anfängt, hat man diese Fehlerquelle zumindest reduziert.

Was man sonst noch machen könnte, wäre bei grösseren Datenmengen darauf achten das man „lazy“ programmiert, also wo es geht Iteratoren, Generatorfunktionen und -ausdrücke verwenden, um möglichst wenig Daten gleichzeitig im Speicher halten zu müssen.
Serpens66
User
Beiträge: 259
Registriert: Montag 15. Dezember 2014, 00:31

Das Betriebssystem ist übrigens Debian 8.1, hatte ich vergessen zu erwähnen ^^

Danke für die Tipps bisher.
Ich werde die Tage mal ein Beispiel schreiben, wie ich das mit dem leeren dictionary meine und mache. Dann könnt ihr ja scheiben, ob das so okay ist, oder falsch.

Allerdings habe ich eben auch nochmal den top Befehl eingegeben, während mein Skript nicht lief. Könnt ihr mir sagen, warum dennoch soviel Arbeitsspeicher verbraucht wird, oder sollte ich mich da an ein anderes Forum wenden, weil das nicht direkt mit Python zusammenhängt?

Code: Alles auswählen

top - 19:20:01 up 7 days, 18:32,  1 user,  load average: 0.01, 0.10, 0.17
Tasks:  65 total,   1 running,  64 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.3 us,  0.3 sy,  0.0 ni, 98.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.7 st
KiB Mem:    506340 total,   331300 used,   175040 free,    61524 buffers
KiB Swap:        0 total,        0 used,        0 free.   208172 cached Mem

  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
13018 root      20   0   82668   5948   5096 S  0.3  1.2   0:00.95 sshd
18633 root      20   0   23528   2796   2356 R  0.3  0.6   0:00.83 top
    1 root      20   0   28556   3844   2252 S  0.0  0.8   0:14.87 systemd
    2 root      20   0       0      0      0 S  0.0  0.0   0:00.00 kthreadd
    3 root      20   0       0      0      0 S  0.0  0.0   2:35.51 ksoftirqd/0
    5 root       0 -20       0      0      0 S  0.0  0.0   0:00.00 kworker/0:0H
    7 root      20   0       0      0      0 S  0.0  0.0  10:11.78 rcu_sched
    8 root      20   0       0      0      0 S  0.0  0.0   0:00.00 rcu_bh
    9 root      rt   0       0      0      0 S  0.0  0.0   0:00.00 migration/0
   10 root      rt   0       0      0      0 S  0.0  0.0   0:15.97 watchdog/0
   11 root       0 -20       0      0      0 S  0.0  0.0   0:00.00 khelper
   12 root      20   0       0      0      0 S  0.0  0.0   0:00.00 kdevtmpfs
   13 root       0 -20       0      0      0 S  0.0  0.0   0:00.00 netns
   14 root      20   0       0      0      0 S  0.0  0.0   0:00.56 khungtaskd
   15 root       0 -20       0      0      0 S  0.0  0.0   0:00.00 writeback
   16 root      25   5       0      0      0 S  0.0  0.0   0:00.00 ksmd
   17 root       0 -20       0      0      0 S  0.0  0.0   0:00.00 crypto
   18 root       0 -20       0      0      0 S  0.0  0.0   0:00.00 kintegrityd
   19 root       0 -20       0      0      0 S  0.0  0.0   0:00.00 bioset
   20 root       0 -20       0      0      0 S  0.0  0.0   0:00.00 kblockd
   22 root      20   0       0      0      0 S  0.0  0.0   0:09.40 kswapd0
   23 root      20   0       0      0      0 S  0.0  0.0   0:00.00 fsnotify_mark
   29 root       0 -20       0      0      0 S  0.0  0.0   0:00.00 kthrotld
   30 root       0 -20       0      0      0 S  0.0  0.0   0:00.00 ipv6_addrconf
   31 root       0 -20       0      0      0 S  0.0  0.0   0:00.00 deferwq
   65 root       0 -20       0      0      0 S  0.0  0.0   0:00.00 ata_sff
   66 root      20   0       0      0      0 S  0.0  0.0   0:00.00 khubd
   67 root      20   0       0      0      0 S  0.0  0.0   0:00.00 scsi_eh_0
   68 root       0 -20       0      0      0 S  0.0  0.0   0:00.00 scsi_tmf_0
   70 root      20   0       0      0      0 S  0.0  0.0   0:00.00 scsi_eh_1
   72 root       0 -20       0      0      0 S  0.0  0.0   0:00.00 scsi_tmf_1
   93 root      20   0       0      0      0 S  0.0  0.0   0:27.46 jbd2/vda1-8
   94 root       0 -20       0      0      0 S  0.0  0.0   0:00.00 ext4-rsv-conver
  137 root      20   0       0      0      0 S  0.0  0.0   0:00.00 kauditd
  157 root      20   0   37424   6680   4280 S  0.0  1.3  20:36.26 systemd-journal
  161 root      20   0   40792    476      4 S  0.0  0.1   0:00.07 systemd-udevd
  194 root      20   0       0      0      0 S  0.0  0.0   0:00.00 vballoon
  196 root       0 -20       0      0      0 S  0.0  0.0   0:00.00 kpsmoused
  271 root       0 -20       0      0      0 S  0.0  0.0   0:00.00 kvm-irqfd-clean
  448 root      20   0   37068   1740   1320 S  0.0  0.3   0:04.07 rpcbind
  460 statd     20   0   37268    596      8 S  0.0  0.1   0:00.00 rpc.statd
  476 root       0 -20       0      0      0 S  0.0  0.0   0:00.00 rpciod
  478 root       0 -20       0      0      0 S  0.0  0.0   0:00.00 nfsiod
Dav1d
User
Beiträge: 1437
Registriert: Donnerstag 30. Juli 2009, 12:03
Kontaktdaten:

Das ist normal, siehe auch free command.

Wobei sich mein 'free' schon geändert hat (in ein Format das weniger verwirrend ist):

Code: Alles auswählen

              total        used        free      shared  buff/cache   available
Mem:          15983        2523        9308         171        4152       13182
Swap:             0           0           0
Aber selbst hier kann man leicht sehen (btw das sind nicht bytes sonder MiB), dass used + free != total.
Zuletzt geändert von Dav1d am Dienstag 12. Januar 2016, 21:14, insgesamt 1-mal geändert.
the more they change the more they stay the same
BlackJack

@Serpens66: Wenn Du wissen möchtest was so den Speicher verbraucht, solltest Du Dir das nicht nach Prozessorauslastung sondern nach Speicherverbrauch sortieren lassen.
Benutzeravatar
Sr4l
User
Beiträge: 1091
Registriert: Donnerstag 28. Dezember 2006, 20:02
Wohnort: Kassel
Kontaktdaten:

Beim beobachten vom "free memory" musst du immer aufpassen ob das Tool, welches du verwendest die "buffer and caches" mitrechnet oder rausrechnet. Diese werden automatisch freigegeben wenn ein Programm Arbeitsspeicher benötigt.
Serpens66
User
Beiträge: 259
Registriert: Montag 15. Dezember 2014, 00:31

Sr4l hat geschrieben:Beim beobachten vom "free memory" musst du immer aufpassen ob das Tool, welches du verwendest die "buffer and caches" mitrechnet oder rausrechnet. Diese werden automatisch freigegeben wenn ein Programm Arbeitsspeicher benötigt.
ja stimmt, das stand auch im Link von Dav1d.
Wenn ich "free" eintippe, sehe ich, dass der Wert in der "-/+ buffers/cache" und "free" Spalte recht groß ist, zurzeit, nach kurzer Laufzeit des Services, 340900.
Das scheint ja schon ziemlich viel zu sein, was bei Bedraf freigegeben werden kann.
Merkwürdig nur, dass der Service schon 2 mal gekillt wurde, wenn ich es zu lange am Stück laufen lasse.

Ich könnte vermutlich besser testen, was genau der Auslöser ist, wenn ich sichergehen kann, dass der Service nach dem Kill wieder neu gestartet wird.
Wisst ihr da zufällig, wie man das umsetzen kann?
Denn solange es nicht automatisch neu startet, traue ich mich nicht den Service länger laufen zu lassen, da es nicht gut, ist, wenn der Service nicht mehr läuft (besonders wenn es über Nacht passiert und ich es erst stunden später merke)
BlackJack

@Serpens66: Um Prozesse zu überwachen und gegebenenfalls neu zu starten gibt es natürlich Lösungen. Klassisch so etwas wie daemontools oder supervisord. Bei Debian 8 a.k.a. Jessie kannst Du auch mal `systemd` anschauen, das wird da ja schon verwendet.
Serpens66
User
Beiträge: 259
Registriert: Montag 15. Dezember 2014, 00:31

BlackJack hat geschrieben:@Serpens66: Um Prozesse zu überwachen und gegebenenfalls neu zu starten gibt es natürlich Lösungen. Klassisch so etwas wie daemontools oder supervisord. Bei Debian 8 a.k.a. Jessie kannst Du auch mal `systemd` anschauen, das wird da ja schon verwendet.
hast du ein paar stichworte nach denen ich suchen könnte?
Wenn ich nach "jessie systemd service automatisch starten" suche, kommen in erster Linie ergebnisse, wie man services nach einem system neustart/crash automatisch startet. Aber ich suche ja eine automatischen Neustart, nachdem der Service gekilled wurde.
und "jessie systemd service restart after kill" bringt dieselben ergebnisse... =/

edit:
oder decken die "runlevel" das zufällig auch mit ab?
https://www.digitalocean.com/community/ ... l-examples
Dav1d
User
Beiträge: 1437
Registriert: Donnerstag 30. Juli 2009, 12:03
Kontaktdaten:

Ich persönlich würds mit supervisor machen, richtig gutes Tool, benutze ich für fast alles: Websites, andere Netzwerk-Services, Daemons etc.

Bei solchen sachen wirst du auf Deutsch nicht fündig: 'systemd service auto restart' -> http://www.freedesktop.org/software/sys ... rvice.html (und Ctrl+F restart), btw. man kann auch in der manpage auf dem RPI nachschauen ;).

Trotzdem würde ich dir empfehlen mal supervisord anzuschauen.
the more they change the more they stay the same
BlackJack

@Serpens66: Die systemd.service-Manpage beispielsweise. ``Restart=`` ist da eine relevante Option.
Serpens66
User
Beiträge: 259
Registriert: Montag 15. Dezember 2014, 00:31

Dav1d hat geschrieben:Ich persönlich würds mit supervisor machen, richtig gutes Tool, benutze ich für fast alles: Websites, andere Netzwerk-Services, Daemons etc.

Bei solchen sachen wirst du auf Deutsch nicht fündig: 'systemd service auto restart' -> http://www.freedesktop.org/software/sys ... rvice.html (und Ctrl+F restart), btw. man kann auch in der manpage auf dem RPI nachschauen ;).

Trotzdem würde ich dir empfehlen mal supervisord anzuschauen.
habe mir supervisor mal hier durchgelesen: https://www.digitalocean.com/community/ ... debian-vps
Hab ich das richtig verstanden, dass dies eine Alternative ist, zum Service? Also ich würde das Skript dann nicht so wie jetzt, als Service laufen lassen, sondern eben mit dem supervisor?

Mit der Restart Möglichkeit bei einem "unexpected" Fehler klingt das aufjedenfall gut. Danke :)

Mal schauen, wann ich dafür Zeit habe. Ich befürchte, dass das einige Stunden dauern wird, das einzurichten. In der Regel gibts bei sowas oft Probleme mit der directory :D Also wo ich das Skript und die dazugehörigen Dateien und Skripte hinpacken muss, damit der Zugriff einwandfrei funktioniert. Auch sehe ich im Tutorial noch keine Möglichkeit, die Größe der logs zu verringern... muss ich dann auch noch raussuchen ^^
Serpens66
User
Beiträge: 259
Registriert: Montag 15. Dezember 2014, 00:31

Habe einen Weg gefunden, bei dem der Service nie wegen zu wenig arbeitsspeicher gekilled wird.

Es scheint tatsächlich hieran zu liegen:

Code: Alles auswählen

with open('History.txt', 'a') as f :
                    f.write("%s" % text)
Denn wenn ich täglich die vom Vortag angelegte .txt Datei lösche, kann das Skript problemlos tagelang durchlaufen.
Lösche ich die .txt Datei allerdings nicht, sodass sie zu groß wird, wird der service irgendwann gekilled.

Vllt liegt das daran, dass bei jedem öffnen der datei der gesamte inhalt im arbeitspeicher geöffnet wird?
Falls ja, gibt es eine bessere Art der Öffnung einer txt Datei, um nur untendran etwas hinzuzufügen? Dafür muss ja der ganze kram der vorher geschrieben wurde nicht mit geöffnet werde.
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@Serpens66: nein, die Datei landet nicht im Arbeitsspeicher. Kann es sein, dass es gar nicht am Arbeitsspeicher liegt, sondern dass Deine Disk-Quota überschritten wird?
Serpens66
User
Beiträge: 259
Registriert: Montag 15. Dezember 2014, 00:31

Sirius3 hat geschrieben:@Serpens66: nein, die Datei landet nicht im Arbeitsspeicher. Kann es sein, dass es gar nicht am Arbeitsspeicher liegt, sondern dass Deine Disk-Quota überschritten wird?
Der digitalocean support meinte damals, ich solle "sudo cat /var/log/messages" ausführen und ihm das ergebnis schicken. Hatte ich gemacht und er schrieb daraufhin:
Dec 23 21:57:26 MeinServer kernel: [4790455.532401] python3.4 invoked oom-killer: gfp_mask=0x280da, order=0, oom_score_adj=0
This means that your Droplet is running out of memory which is causing processes to be killed. You will need to figure out what is using so much memory (it could be your python program) to prevent this from happening.
Ich nutze den 5 $/monat Plan https://www.digitalocean.com/pricing/ , also sollte ich doch 20GB freien Speicher haben. Und "memory" heißt doch arbeitsspeicher, oder?

Die textdateien werden schon recht groß. Aktuell lösche ich sie wenn sie ungefähr 500 MB groß sind. Weiß nicht was genau die Schwelle ist, bis der service gekilled wird... könnten 1 bis 2 GB sein. Aber 20 GB sind die txt dateien definitv nicht groß.

Aktuell wird mir folgendes angezeigt (die fragliche txt datei ist gerade nur 66 MB groß):

Code: Alles auswählen

root@MeinServer:~# df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/vda1        20G  2.5G   17G  14% /
udev             10M     0   10M   0% /dev
tmpfs            99M   13M   87M  13% /run
tmpfs           248M     0  248M   0% /dev/shm
tmpfs           5.0M     0  5.0M   0% /run/lock
tmpfs           248M     0  248M   0% /sys/fs/cgroup
BlackJack

@Serpens66: An der Programmzeile kann es aber nicht liegen. Ausser Du speicherst die Datei nicht auf dem Massenspeicher sondern in den Arbeitsspeicher. Speicherst Du die Datei unterhalb irgendeines Pfades der beim ``df`` mit dem Typ `tmpfs` aufgelistet wird? Unter `/run/` beispielsweise?
Antworten