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

Sirius3 hat geschrieben:@Serpens: Am top kann man nicht erkennen, wieviel Speicher im Moment durch Pythonobjekte verbraucht werden, sondern nur einen Peak-Wert, also dass irgend wann einmal 33MB mehr an Speicher verbraucht wurden, was jetzt nicht sonderlich viel ist. Und wie schon mehrfach betont hilft es nicht, von außen nach irgendwelchem Speicherverbrauch zu suchen. Außer wenn irgendwo Werte gecached werden sollte es also nicht zu unnötigem Speicherverbrauch kommen, der nicht auch wieder aufgeräumt wird.
und trotzdem wird mein service nach 1-3 Tagen Laufzeit gekilled.
Also was kann ich tun?

Also sagst du, dass es wohl an gecacheden Werten liegt? Was heißt gecachede werte? Es gibt ja den pychache. Ich glaube das bedeutet, dass dort eine Kopie der Python scripte zwischengespeichert wird, um schnell drauf zuzugreifen, richtig? Aber das ist ja ebenfalls einmalig und sollte nicht immer mehr werden.
Wann werden noch Dinge gecached? das einzige was da für mich plausible klingt, ist das schreiben der txt Datei, aber das hattest du ja schon ausgeschlossen.
BlackJack

@Serpens66: Ich denke mal Sirius3 meint ausser wenn in Deinem Programm irgendwo Werte gecached werden. Entweder von Dir explizit oder von irgendeiner Bibliothek die Du verwendest. Da kann man aber jetzt wieder nicht viel sagen wenn man das Programm nicht kennt.

Hattest Du eigentlich den Speicherverbrauch mal kontinuierlich protokolliert? Steigt der innerhalb der drei Tage gleichmässig immer mehr an, oder kann es auch sein das nur kurz vorm Abschuss etwas passiert was ganz schnell den ganzen Speicher verbraucht? Also zum Beispiel Eingabedaten kommen bei denen das ausgelöst wird, entweder ”normal”, oder aber auch vielleicht durch einen Fehler in Deinem Programm‽
Serpens66
User
Beiträge: 259
Registriert: Montag 15. Dezember 2014, 00:31

BlackJack hat geschrieben:@Serpens66: Ich denke mal Sirius3 meint ausser wenn in Deinem Programm irgendwo Werte gecached werden. Entweder von Dir explizit oder von irgendeiner Bibliothek die Du verwendest. Da kann man aber jetzt wieder nicht viel sagen wenn man das Programm nicht kennt.

Hattest Du eigentlich den Speicherverbrauch mal kontinuierlich protokolliert? Steigt der innerhalb der drei Tage gleichmässig immer mehr an, oder kann es auch sein das nur kurz vorm Abschuss etwas passiert was ganz schnell den ganzen Speicher verbraucht? Also zum Beispiel Eingabedaten kommen bei denen das ausgelöst wird, entweder ”normal”, oder aber auch vielleicht durch einen Fehler in Deinem Programm‽
Danke für die weiteren Ratschläge :)

Ich dachte eig , dass ich mit dem top befehl den aktuellen verbrauch sehen könnte. Wusste nicht, dass das nur ein peak ist.

Wie protokolliere ich denn den speicherverbrauch am besten? Vermutlich direkt im Skript, also stündlich den speicherverbrauch mit in der txt Datei speichern. Was ist dafür am besten geeignet? Ich gehe aber stark davon aus, dass es ein kontinuirlicher Prozess ist. Denn bei unter einem Tag Laufzeit ist noch nie ein Kill passiert.

bezüglich cache:
Könnt ihr denn Beispiele oderso nennen, wonach ich ausschau halten muss, um chache zu entdecken? Oder einen anderen weg, wie ich feststellen kann, ob eine von mir verwendete bibliothek cached? Da es ja keine meiner Variablen ist, die so groß wird, wäre das wohl die nächste Vermutung, dass es an einer bibliothek liegen könnte.
Abgesehen von Skripten für die API Anbindung, welche in der Regel requests verwenden, importiere ich noch:

Code: Alles auswählen

import requests
import json
from time import sleep
import time
import threading
import sys
import traceback
from random import randint
import math
from copy import deepcopy  ## sollte nicht zuoft verwendet werden, da ausführung lange dauert !! 
from decimal import *
edit:
die Klassen der api skripte werden nach dem import direkt geöffnet und offen gehalten, also
Api1 = apiskript.Apiklasse()
Ist es dann sinvoll, diese auch noch in die stündliche memory kontrolle reinzupacken, also sys.getsizeof(Api1) zu prüfen? Oder kann da eig nichts passieren? (sind eigentlich alles nur funktionen ohne self. variablen, also eig sollte da nach jeder Verwednung alles gelöscht werden)
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

@Serpens66: was sollen denn API-Klassen sein? Funktionen in Klassen zu packen, wenn es kein self gibt ist ziemlich sinnfrei.
Serpens66
User
Beiträge: 259
Registriert: Montag 15. Dezember 2014, 00:31

Sirius3 hat geschrieben:@Serpens66: was sollen denn API-Klassen sein? Funktionen in Klassen zu packen, wenn es kein self gibt ist ziemlich sinnfrei.
keine ahnung. Die skripte die ich fertig gefunden habe hatten das alle. In der Regel gibt es self.secret und self.apikey. (also diese self Variablen gibt es schon) Für Skripte die ich dann geschrieben habe, hab ich das so übernommen.
Ich glaube ohne klasse könnte ich das skript im hauptskript eben nicht einmal mit apikey übergabe öffnen, sondern müsste dann überall immer wieder den key usw wiederholen.

Aber wie dem auch sei, das ist ja nicht das Thema...
Also wie überwache ich automatisch am sinnvollsten den Speicherverbrauch? Welcher Befehl gibt den aktuell genutzten Speicher des ganzen Skripts aus? Klar gibt es beim memory_profiler einen zeitlichen check und darstellung, aber memory_profiler scheint sehr ungeeignet für große skripte die über längere Zeit laufen sollen und es auf schnelligkeit ankommt.
BlackJack

@Serpens66: Das `psutils`-Modul würde sich IMHO anbieten.
Serpens66
User
Beiträge: 259
Registriert: Montag 15. Dezember 2014, 00:31

BlackJack hat geschrieben:@Serpens66: Das `psutils`-Modul würde sich IMHO anbieten.
psutil.virtual_memory() , bzw das hier? https://github.com/giampaolo/psutil/blo ... meminfo.py
Ist das denn ausreichend? Da ja nichts zum verbrauchten Speicher vom skript selbst dort steht.
BlackJack

@Serpens66: Nein ich meine das `Process`-Objekt für den Dienst nach dem Speicherverbrauch fragen. Das liefert dann auch nur die Daten für den Prozess. Entweder im Dienst selbst, oder extern, dann müsste man irgendwie an die Prozess-ID kommen oder sich das anhand anderer Kriterien aus den laufenden Prozessen fischen.
Benutzeravatar
miracle173
User
Beiträge: 127
Registriert: Samstag 6. Februar 2016, 00:28

@Serpens66
Als einfache Maßnahme würde ich empfehlen, einmal am Tag alle Variablen und ihre Werte in eine Datei auszugeben.
http://stackoverflow.com/questions/2196 ... out-specif
schlägt da die Funktionen locals() und globals() vor.
Wenn die Dateien von Tag1, Tag2 und Tag3 etwa gleich gross sind, dann bringt dieser Ansatz nichts. Wenn die Größe wesentlich zunimmt, dann vergleichst du die Dateiinhalte und versuchst herauszufinden, an welcher Variable das liegt.
Wenn dein Programm eine große Schleife enthält, wäre der Beginn der Schleife möglicherweise ein geeigneter Ort für die Ausgabe.
Das Modul pprint erleichtert eine lesbare Ausgabe zu erzeugen.
Serpens66
User
Beiträge: 259
Registriert: Montag 15. Dezember 2014, 00:31

BlackJack hat geschrieben:@Serpens66: Nein ich meine das `Process`-Objekt für den Dienst nach dem Speicherverbrauch fragen. Das liefert dann auch nur die Daten für den Prozess. Entweder im Dienst selbst, oder extern, dann müsste man irgendwie an die Prozess-ID kommen oder sich das anhand anderer Kriterien aus den laufenden Prozessen fischen.
danke, ich würd das dann erstmal versuchen intern im skript selbst zu machen.
okay, also das was hier https://pypi.python.org/pypi/psutil unter Process Maanagement steht.
Da gibts ja einiges... würden vermutlich auch mehrere Dinge in Frage kommen...
psutil.test() sieht aber ganz gut aus, oder? Kann ich das verwenden, bzw hilft das in diesem Fall?
Oder besser memory_info() bzw. memory_full_info()?
miracle173 hat geschrieben:@Serpens66
Als einfache Maßnahme würde ich empfehlen, einmal am Tag alle Variablen und ihre Werte in eine Datei auszugeben.
http://stackoverflow.com/questions/2196 ... out-specif
schlägt da die Funktionen locals() und globals() vor.
Wenn die Dateien von Tag1, Tag2 und Tag3 etwa gleich gross sind, dann bringt dieser Ansatz nichts. Wenn die Größe wesentlich zunimmt, dann vergleichst du die Dateiinhalte und versuchst herauszufinden, an welcher Variable das liegt.
Wenn dein Programm eine große Schleife enthält, wäre der Beginn der Schleife möglicherweise ein geeigneter Ort für die Ausgabe.
Das Modul pprint erleichtert eine lesbare Ausgabe zu erzeugen.
meinst du ein stumpfes übernehmen dieses debug_nice skriptes zusammen mit dem example auf Pastebin würde den zweck erfuellen? http://pastebin.com/USVw11bJ
BlackJack

@Serpens: `psutil.test()` sieht ganz gut aus wenn man die Daten von allen laufenden Prozessen als Text ausgeben möchte. Das ist a) ein bisschen viel und b) müsste man das wenn man es weiterverarbeiten möchte, zum Beispiel um Verläufe zu plotten, erst wieder parsen. Man möchte ja eigentlich lieber beispielsweise eine CSV-Datei mit den Werten erstellen, die man hinterher einfach auswerten kann.
Serpens66
User
Beiträge: 259
Registriert: Montag 15. Dezember 2014, 00:31

BlackJack hat geschrieben:@Serpens: `psutil.test()` sieht ganz gut aus wenn man die Daten von allen laufenden Prozessen als Text ausgeben möchte. Das ist a) ein bisschen viel und b) müsste man das wenn man es weiterverarbeiten möchte, zum Beispiel um Verläufe zu plotten, erst wieder parsen. Man möchte ja eigentlich lieber beispielsweise eine CSV-Datei mit den Werten erstellen, die man hinterher einfach auswerten kann.
Okay, also was stattdessen? Bei process.memory_info() bin ich mir nicht sicher, ob das ist was wir brauchen, da ich mit den Begriffen rss, vms usw nicht soviel anfangen kann. Ist es das?
Benutzeravatar
miracle173
User
Beiträge: 127
Registriert: Samstag 6. Februar 2016, 00:28

Serpens66 hat geschrieben: (...)
meinst du ein stumpfes übernehmen dieses debug_nice skriptes zusammen mit dem example auf Pastebin würde den zweck erfuellen? http://pastebin.com/USVw11bJ
Hallo
Keine Ahnung,l möglicherweise, ich kenne das nicht.

Angenommen du hast eine Variable die als Wert eine Liste hat. Wenn du an diese Liste Werte anhängst und vergisst, die Liste am beginn jedes Schleifendurchlaufs zu leeren, dann wird diese Liste immer länger. Der Wert der Variablen, also die Liste, wird durch locals() ausgegeben. Wenn du also locals nach einem Schleifeendurchlauf ausgibst, wir die entstehende Datei immer grösser, da ja diese Liste immer länger wird. Wenn die Datei, in die du locals schreibts, immer grössert als die vorherige wird, dann hast du also eine Variable, deren Wert im Laufe derzeit immer grösser (besser: länger) wird. Dass ist dann dein Memory leak. Dieser Test ist ja schnell implementiert und darum habe ich dir das empfohlen.

Wenn es aber nicht der Wert einer Variablen ist, der immer grössert wird, dann wirst du das mit dieser Methode nicht finden. Keine Variable bedeuted, es ist etwas auf das Referenziert wird, z.b. ein Objekt.

Ich hab das noch das gefunden. Das schaut auf den ersten Blick ganz nützlich aus:
https://pythonhosted.org/Pympler/tutori ... orial.html
Benutzeravatar
miracle173
User
Beiträge: 127
Registriert: Samstag 6. Februar 2016, 00:28

Serpens66 hat geschrieben: (...)
Ich dachte eig , dass ich mit dem top befehl den aktuellen verbrauch sehen könnte. Wusste nicht, dass das nur ein peak ist.
(...)
Der top Befehl zeigt dir an, wieviel Memory dem Prozess aktuell zugeordnet ist. Allerdings gibt ein Linux-Prozess angefordertes Memory erst dann ans Betriebssystem zurück, wenn er beendet wird. Das heißt, auch wenn das Python-Programm wieder Memory frei gibt, wird das nicht vom Prozeß ans Betriebssystem zurückgegeben. Aber es wird dann wiederverwemder, wenn das Python-Programm wieder mehr Memory braucht. Insofern stellt der aktuelle Memory-Verbrauch des Prozesses, der von top angezeigt wird dem Peak-Value des Memory-Verbrauch des Python Programms dar.
Serpens66
User
Beiträge: 259
Registriert: Montag 15. Dezember 2014, 00:31

@miracle173: ich danke dir für deine Antworten :
Pympler sieht ganz gut aus, besonders der teil hier: https://pythonhosted.org/Pympler/muppy.html#muppy
Ich koennte also alles paar stunden so eine summary erstellen lassen und damit schauen, was so passiert. Werde das wenn ich etwas Zeit habe mal einbauen. Melde mich dann wieder mit dem Ergebnis oder eventuellen Problemen :)
Benutzeravatar
miracle173
User
Beiträge: 127
Registriert: Samstag 6. Februar 2016, 00:28

miracle173 hat geschrieben: (...)
Allerdings gibt ein Linux-Prozess angefordertes Memory erst dann ans Betriebssystem zurück, wenn er beendet wird.
(...)
Möglicherweise ist das nicht mehr ganz richtig, da nicht nur der brk System-Call zum Anfordern von Memory verwendet werden kann, sondern auch mmap, und das erlaubt auch die Rückgabe ans Betriebssystem mit dem Call von munmap.
Benutzeravatar
miracle173
User
Beiträge: 127
Registriert: Samstag 6. Februar 2016, 00:28

BlackJack hat geschrieben:@Serpens66: Das `psutils`-Modul würde sich IMHO anbieten.
Da braucht man nicht unbedingt zusätzliche Python-Module installieren. Die Tools, die auf einem Linuxrechner vorhanden sind, reichen aus.

Entweder top im Batch-Modus

Code: Alles auswählen

$ top -b -p 1 -d 3
top - 00:10:59 up 4 days, 22:04,  8 users,  load average: 14.23, 10.05, 7.98
Tasks:   1 total,   0 running,   1 sleeping,   0 stopped,   0 zombie
Cpu(s): 15.8%us,  2.2%sy,  0.1%ni, 81.6%id,  0.0%wa,  0.0%hi,  0.2%si,  0.0%st
Mem:  264085904k total, 236958216k used, 27127688k free,  1114120k buffers
Swap: 25165820k total,     2392k used, 25163428k free, 87088412k cached

   PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
     1 root      20   0 19404 1596 1276 S  0.0  0.0  51:03.93 init


top - 00:11:02 up 4 days, 22:04,  8 users,  load average: 14.45, 10.16, 8.03
Tasks:   1 total,   0 running,   1 sleeping,   0 stopped,   0 zombie
Cpu(s): 41.3%us,  1.1%sy,  0.0%ni, 57.3%id,  0.0%wa,  0.0%hi,  0.2%si,  0.0%st
Mem:  264085904k total, 237189644k used, 26896260k free,  1114120k buffers
Swap: 25165820k total,     2392k used, 25163428k free, 87088520k cached

   PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
     1 root      20   0 19404 1596 1276 S  0.0  0.0  51:03.93 init
oder ps

Code: Alles auswählen

ps aux|head -1; ps aux|grep /sbin/init|grep -v grep
USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root          1  0.7  0.0  19404  1596 ?        Ss   Mar15  51:04 /sbin/init
BlackJack

@miracle173: Ja klar, man kann auch alles als Bash-Skript schreiben und dann Python ganz weglassen. Wenn ich den Speicherverbrauch eines Python-Programms vom Programm selber aus überwachen möchte und die Wahl habe externe, plattformabhängige Prozesse zu starten oder ein Python-Modul zu verwenden, das zudem auch noch auf mehr als einer Plattform läuft, nehme ich jedenfalls lieber das Modul. Zumal der OP das wegen dem Memory Profiler ja wohl sowieso schon installiert hatte.
Serpens66
User
Beiträge: 259
Registriert: Montag 15. Dezember 2014, 00:31

Serpens66 hat geschrieben:@miracle173: ich danke dir für deine Antworten :
Pympler sieht ganz gut aus, besonders der teil hier: https://pythonhosted.org/Pympler/muppy.html#muppy
Ich koennte also alles paar stunden so eine summary erstellen lassen und damit schauen, was so passiert. Werde das wenn ich etwas Zeit habe mal einbauen. Melde mich dann wieder mit dem Ergebnis oder eventuellen Problemen :)
Hallo zusammen, da bin ich wieder...
Ich habe das Thema so lange ruhen lassen, weil ich nun wie zuvor irgendwo empfohlen wurde, einen automatischen restart in den service eingebaut habe. So wurde der Service also nach ~3 Tagen gekilled, aber sodort wieder neugestartet und das war für mich okay.
Doch nun würde ich das verusrachende Skript gerne 5 mal gleichzeitig mit unterschiedlichen Einstellungen laufen lassen (mithilfe von Multithread)
Dabei habe ich nun leider gemerkt, dass der Kill nun sogar schon nach ~4 Stunden erfolgt. Und das ist mir trotz Neustart doch etwas zu schnell..

Also habe ich jetzt Pympler probiert:

Ca. alle 5 minuten schreibt mein Skript die summary in eine txt Datei (self.memhistory ist für das schreiben in die Datei mit Datum verantwortlich):

Code: Alles auswählen

self.memhistory('\n'.join(summary.format_(summary.summarize(muppy.get_objects()))))
5 Minuten vor einem Kill, sah die Summary so aus:

Code: Alles auswählen

                               types |   # objects |   total size
==================================== | =========== | ============
                         <class 'str |      103292 |      8.79 MB
                        <class 'list |       28184 |      4.78 MB
                        <class 'dict |        5762 |      4.33 MB
                        <class 'type |         725 |    739.23 KB
                        <class 'code |        4779 |    672.24 KB
                       <class 'float |       14536 |    340.69 KB
                         <class 'set |         403 |    169.66 KB
                       <class 'tuple |        1878 |    125.44 KB
                     <class 'weakref |        1418 |    110.78 KB
                         <class 'int |        3256 |     91.18 KB
          <class 'wrapper_descriptor |        1146 |     89.53 KB
  <class 'builtin_function_or_method |        1104 |     69.00 KB
           <class 'method_descriptor |         957 |     67.29 KB
           <class 'getset_descriptor |         790 |     55.55 KB
                 <class 'abc.ABCMeta |          51 |     48.95 KB
Genau so, sah sie aber auch schon stunden zuvor aus... woraus ich schließe, dass hier nicht die Ursache des kills zu finden ist =/

Nun also 2 Fragen:
1) Es könnte theoretisch ja noch ein ruckartiger anstieg sein, der nur im moment des Kills auftritt. Wie würde ich diesen am besten sichtbar machen? Ich könnte anstelle von alle 5 Minuten , alle 20 sekunden so eine summary machen und den "diff" Befehl von pympler verwenden. Nur weiß ich grad nicht, wie ich dabei dann prüfen kann " fals diff >= x" um einen starken anstieg festzustellen...
Aber würde das was bringen? Angenommen es ist wirklich ein Fehler, der in kürzester Zeit tonnenweise Speicher verbraucht, dann wird der wohl auftreten und sofort zum kill führen, ohne dass ich den memory verbrauch vorher checken könnte... ?!

2) Ich verwende, um das Skript in 5 Einstellungen gleichzeitig laufen zu lassen multithreads:

Code: Alles auswählen

class MainClass():
    #...
    def parallel(self,aufgabenliste):
        threads = list(map(Multithread, aufgabenliste)) 
        for thread in threads: 
            thread.start()
       
        for thread in threads
            thread.join()
        return(list(thread.result for thread in threads))  

class Multithread(threading.Thread):
    def __init__(self, aufgabe=0):
        if aufgabe:
            threading.Thread.__init__(self)
            self.aufgabe = aufgabe[0]  #ist ja ein Tupel aus funktion und argumenten
            self.argumente = aufgabe[1]   
            self.result = None  
        return
        
    def run(self):
        self.result = self.aufgabe(*self.argumente)  
        return
Die Funktion die dann mit unterschiedlichen einstellungen laufen soll, wird über die "parallel" funktion aufgerufen und läuft dann in einer Endlosschleife. Die einzige Änderung ist also, dass anstelle von einer Endlosschleife, nun insg 5 endlosschleifen gleichzeitig laufen.
Die memory summary habe ich nur in einer dieser einstellungen erlaubt, weil ich davon ausgehe, dass es die memory vom gesmaten Skript, also auch den anderen 4 schleifen zeigt. Ist das richtig?
Könnte der Memory Verbrauch evlt auch mit der verwendung der Threads zu tun haben?
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

@Serpens66: wenn es kein kontinuierlicher Anstieg ist, dann hilft es, wie Du ja bereits vermutest, nichts, das Intervall zu kürzen. Wildes Herumraten, wie schon vor einem halben Jahr, hat Dich ja auch nicht weiter gebracht. Entweder Du identifizierst selbst Stellen, an denen potentiell viel Speicher verbraucht werden könnte (Speicher, der abhängig von einer Variable reserviert wird, while-Schleifen, die Listen erzeugen und möglicherweise die Abbruchbedingung nie erfüllt wird) und gibst genau dort passende Debugausgaben aus. Oder zeig einfach, was Du da versuchst, dann finden wir gemeinsam viel schneller die problematischen Stellen.
Antworten