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

Dadurch, dass der Kill nun deutlich schneller vonstatten geht, erstaunlicherweise gerade sogar schon nach wenigen Minuten nachdem das Skript neu gestartet wurde, lässt es sich zumindest etwas leichter untersuchen...

In den allermeisten Fällen findet der Kill nicht mitten in meinem riesigen Skript statt, sondern an derselben überschaubaren Stelle, die aber eig kein Problem darstellen sollte, oder doch?

Und zwar läuft das komplette Skript ganz simpel in einer "while True" Dauerschleife:

Code: Alles auswählen

#!/usr/bin/python3.4
# coding: utf8

from pympler import summary, muppy
import requests
import json
from time import sleep
import logging
import time
import threading
import sys
import traceback
from random import randint
import math
from copy import deepcopy
from decimal import *  

def uhrzeit():
    return(time.asctime())
class MyMain:
    def memhistory(self,text):
        text = str(text) 
        textmituhrzeit = "{} ########################################\n".format(uhrzeit())+text+"\n"  # ich weiß unnötig kompliziert
        with open('MemHistory.txt', 'a') as f : 
                f.write("%s" % textmituhrzeit)

    def vorsuchen(self):
        memreporten = 20
        while True:
            self.history("vor neuem durchlauf",0,0,1)  # self.history schreibt text in eine txt datei
            
            # suchen() ist die fkt die in dauerschleife laufen soll. Ein Durchlauf dauert durch Verwendung von time.sleep mindesten 5.
            ret = self.suchen() # wenn unwahr, weiter schleife. wenn wahr, beenden
            
            self.history("nach durchlauf vor memreport",0,0,1)
            if memreporten>=1: # ursprünglich 20
                self.memhistory('\n'.join(summary.format_(summary.summarize(muppy.get_objects())))) # memory info in txt datei speichern
                memreporten = 0
            memreporten += 1
            self.history("nach durchlauf und memreport",0,0,1)
            
            if ret:
                self.history("suchen fkt beenden",0,0,1)
                return
                
if __name__ == '__main__':  
    My = MyMain() 
    try:    
        My.vorsuchen() 
    except Exception as err:
        My.FehlerBeenden() # kontrolliertes beenden
        raise err
    sys.exit()
Nachdem der service gekilled wurde, ist der letzte Eintrag in meiner history txt Datei lustigerweise meistens (90% der Fälle, die andern 10% konnte ich noch nicht genau identifizieren):
nach durchlauf vor memreport
Das wirft die Vermutung auf, dass memhistory die Ursache ist, was aber nicht sein kann, da der Kill ja auch ohne das auftritt. Aber möglicherweise beschleunigt es den Kill und man kann so vllt auf eine mögliche Ursache schließen? memhistory wird jedenfalls nicht mehr vollständig ausgeführt, in der .txt Datei steht zum zeitpunkt des kills keine info.

Einzige Vermutung die sich daraus nun ergibt, ist dass das dauernde öffnen und schreiben in txt Dateien Probleme machen kann?
Zuletzt geändert von Anonymous am Samstag 15. Oktober 2016, 09:22, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
Serpens66
User
Beiträge: 259
Registriert: Montag 15. Dezember 2014, 00:31

Ergänzung:
Nachdem ich den memhistory Teil auskommentiert habe, gab es bei gleicher konfiguration jetzt 8 stunden keinen kill mehr. Ich komme also zum "normal" Zustand zurück, wo es einige Stunden dauert. Mit dem memhistory Ding ging das ja innerhalb von Minuten O.ô.
Die eigentlich verursachende Stelle im Code habe ich noch nicht gefunden, werde aber weiter versuchen es einzugrenzen.

Dennoch denke ich, wäre es gut zu wissen, warum memhistory so unglaublich schnell die memory auslastet, ohne dass die memory summary irgendwelche Anzeichen dazu zeigt?! Eventuell ist das Problem dem wir auf der Spur sind ähnlich gelagert?
garreth
User
Beiträge: 41
Registriert: Donnerstag 23. Oktober 2014, 12:04

Hallo Serpens66,

vielleicht solltest du mal damit anfangen, dass du dein Script aufräumst. Beispiel:

Code: Alles auswählen

def uhrzeit():
    return (time.asctime())
def memhistory(self, text):
    text = str(text)
    textmituhrzeit = "{} ########################################\n".format(
        uhrzeit()) + text + "\n"  # ich weiß unnötig kompliziert <!--- !!!
    with open('MemHistory.txt', 'a') as f:
        f.write("%s" % textmituhrzeit)
Die acht Zeilen, die du brauchst könntest du durch diesen Dreizeiler hier ersetzen(Bitte nicht in die Klasse packen, ist unnötig!):

Code: Alles auswählen

def write_memory_log(text):
    with open('MemHistory.txt', 'a') as f:
        f.write('{0} {1}\n{2}\n'.format(time.asctime(), str(text), '#'*40))
Du kannst deinen Code leserlicher gestalten, indem du ihm eindeutige Namen gibst und auf dein deutsch/englisch Mischmasch verzichtest. Versuche deinen Code einheitlich zu gestalten(z. B. nicht immer zwischen " " und ' ' zu wechseln). Halte dich an den Style Guide for Python Code(CamelCase nur bei Klassen, Variablen etc. mit _).
Wenn du so viele Dateizugriffe hast und dort den Fehler vermutest, würde es dir vielleicht helfen auf eine Datenbank zurück zu greifen.
Antworten