Speicherverbrauch Tkinter

Fragen zu Tkinter.
Antworten
hpr
User
Beiträge: 8
Registriert: Donnerstag 7. April 2011, 09:05

Hi,

habe ein kleines Testprogramm auf Lenny_amd64 Stable (Python 2.5.2) laufen lassen. Alles IO.
Auf Squeeze_amd64 (Python2.6.6) wird der Speicher (Top, VIRT und RES) immer mehr verbraucht.
Im echten Programm werden die Ressourcen (4 GB Hauptspeicher +Swap) innerhalb eines Wochenendes verbraten. Der Rechner ist dann kaum noch bedienbar.
Im Beispiel habe ich Pme eliminiert.
Mit "loop" sieht man das Problem am schnellsten.

Code: Alles auswählen

title = 'Text Speicher'

import time

import Tkinter
class Demo:
    def __init__(self, parent):
        self.parent=parent

        self.sb= Tkinter.Scrollbar(parent)
        self.st = Tkinter.Text(parent,yscrollcommand=self.sb.set,undo='false',maxundo=1)
        self.writeButton = Tkinter.Button(root, text = 'write', command = self.write)
        self.clearButton = Tkinter.Button(root, text = 'clear', command = self.clear)
        self.loopButton = Tkinter.Button(root, text = 'loop', command = self.loop)

        self.sb.pack(side=Tkinter.RIGHT, fill=Tkinter.Y)
        self.st.pack(padx = 5, pady = 5, fill = 'both', expand = 1)
        self.writeButton.pack(side=Tkinter.LEFT)
        self.clearButton.pack(side=Tkinter.LEFT)
        self.loopButton.pack(side=Tkinter.LEFT)
    
#################################################################



    def write(self):
        print 'You clicked on write'
        for i in range(5000):
           self.st.insert('end',str(i)+"xxxxxxxxxxxxxxxxxxxxxx\n")
           self.st.see('end')
        self.st.update()



    def clear(self, num=0):
        print 'You clicked on  clear'
        self.st.delete('1.0','end')
        self.st.insert('end',num)
        self.st.insert('end',"\n")
        self.st.update()
        self.st.clipboard_clear()

    def loop(self):
        for x in range(200):
                self.write()
                time.sleep(1)
                self.clear(x) 

######################################################################

# Create demo in root window for testing.
if __name__ == '__main__':
    root = Tkinter.Tk()
    root.title(title)

    exitButton = Tkinter.Button(root, text = 'Exit', command = root.destroy)
    exitButton.pack(side = 'bottom')
    widget = Demo(root)
    root.mainloop()

BlackJack

@hpr: Also unter einem 32-Bit Ubuntu 10.04 ist auch alles in Ordnung:

Code: Alles auswählen

Python 2.6.5 (r265:79063, Apr 16 2010, 13:09:56) 
[GCC 4.4.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import Tkinter
>>> Tkinter.TkVersion
8.5
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Also ich weiß auch nicht wo das Problem liegen soll, zugegeben im extrem Test schafft man es den RAM vollzubekommen, da der GC nicht hinterherkommt bzw. den Speicher noch nicht freigeben will. Aber voher macht der CPU schlapp da das wrappen des Textes auf das Tk enorme Last verursacht. Jedes Zeichen entspricht laut Tk-Faustregel 2-3 Bytes.

Dennoch kann man deinen Code um einiges Optimieren.
1. Wichtig: Eine Schleife macht das Text-Widget langsam füg immer den gesammten Text ein.
2. Es gibt schon ein Text-Widget mit Scrollbars im Modul ScrolledText.
3. Nach dem löschen "\n" einzufügen ist ziemlich Sinnlos genau wie das "update" und "clipboard_clear" danach.

Hier mal ein sauberes Beispiel wie man es machen könnte:

Code: Alles auswählen

!#/usr/bin/env python
import Tkinter as tkinter
import ScrolledText as tktext

class MyText(tkinter.Frame):
    
    def __init__(self, master, **kw):
        tkinter.Frame.__init__(self, master, kw)

        self.text = tktext.ScrolledText(master)
        self.text.pack(expand=True, fill="both")

        menu = tkinter.Frame(master)
        menu.pack(expand=True, fill="x")
        
        button_clear = tkinter.Button(menu, text="clear", command=self.clear)
        button_clear.pack(fill="y", side="left")

    def write(self, text):
        self.text.insert("end", text)
        self.text.see("end")
        
    def clear(self):
        self.text.delete('1.0', 'end')


def _loop(mytext, text):
    for _ in xrange(200000):
        mytext.clear()
        mytext.update()
        mytext.write("\n".join(20000*(text,)))
        mytext.update()
    
if __name__ == '__main__':
    text = """
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod
tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At
vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd
gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum
dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor
invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero
eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren,
no sea takimata sanctus est Lorem ipsum dolor sit amet."""
    
    root = tkinter.Tk()
    root.title("MyText")

    mytext = MyText(root)
    mytext.pack(expand=True, fill="both")
    
    command_write = lambda: mytext.write("\n".join(20000*(text,)))
    button_write = tkinter.Button(mytext, text="write", command=command_write)
    button_write.pack(fill="y", side="left")
    
    button_loop = tkinter.Button(mytext, text="loop")
    button_loop.config(command=lambda: _loop(mytext, text))
    button_loop.pack(fill="y", side="left")

    button_exit = tkinter.Button(mytext, text='exit', command=root.destroy)
    button_exit.pack(fill="y", side="left")
    
    root.mainloop()
Die "_loop"-Funktion halte ich immer noch für unsinnig, aber damit du einen Vergleich hast. Wenn es schneller gehen soll solltest du dort die erste "update"-Funktion entfernen, die habe ich nur eingebaut damit man es flackern sieht. Ist aber eigentlich ziemlich Sinnfrei. Mindestens eines braucht man dort aber, sonst frisst sich die GUI fest. Generell sollte man "update" nur selten verwenden.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
hpr
User
Beiträge: 8
Registriert: Donnerstag 7. April 2011, 09:05

erst mal Danke,

mein Problem ist der Speicher weshalb ich z.B. Pmw welches ich eigentlich verwendet habe für das Beispiel gelöscht habe. Das Problem Tritt auch auf Suse 11.3_64 auf, auf Suse10.2_32 nicht.

Es soll die möglichkeit bestehen Prozessdaten die aus einem anderen Thread zyklisch kommen zu betrachten und erforderlichenfalls zu scrollen. Das ganze natürlich auf 20 Tab's. Performaceprobleme gibt es nicht, Nur ohne Speicher...
Sicherlich könnte man den Text selbst verwalten und per scrollbutton den gewünschten Text darstellen, aber bisher lief mein Programm auf NT4 ohne Probleme. Entwickelt hatte ich es auf Suse9.0_32. Nun habe ich den NT4 Rechner in Rente geschickt und einen aktuelle Rechner auf Lennu_64 aufgesetzt (vorgenannte Speicherprobleme). Um die Probleme zu analysieren habe ich Sqeeze_64 verwendet und dabei bin ich jetzt am verzweifeln, dabei habe ich auch weitere varianten als clear, update usw schon probiert, aber ohne Erfolg.
BlackJack

@hpr: Du könntest ja mal versuchen das Testprogramm in Tk/Tcl zu programmieren um das Problem weiter einzugrenzen. Wenn das auch Speicher frisst, wäre Python's Tk-Anbindung schon mal aus der Gleichung raus.
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Eventuell auch erstmal mein Schnipsel auf den Plattformen ausprobieren, bevor man das ganze in Tk/Tcl schreibt. Wenn mein Programm nicht diese Speicherprobleme auslöst, kann es nur noch pmw oder deinem Quellcode liegen.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
hpr
User
Beiträge: 8
Registriert: Donnerstag 7. April 2011, 09:05

@Xynon1,

nach
  • 1c1
    < #!/usr/bin/env python
    ---
    > !#/usr/bin/env python
    3c3
    < import Pmw as tktext
    ---
    > import ScrolledText as tktext

bringt dein Programm unter Sqeeze_amd64 leider auch das Speicher Problem.
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

import Pmw as tktext :? - ist eine ziemlich ulkige Zeile

Zudem, kannst du mal die Fehlermeldung posten ?
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
hpr
User
Beiträge: 8
Registriert: Donnerstag 7. April 2011, 09:05

@Xynon1,

bei "import ScrolledText as tktext" gibt es in deinem Script den Fehler
Traceback (most recent call last):
File "BS_ex.py", line 49, in <module>
mytext = MyText(root)
File "BS_ex.py", line 11, in __init__
self.text = tktext.ScrolledText(master)
AttributeError: 'module' object has no attribute 'ScrolledText'
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Logisch, du hast ja auch "import Pmw as tktext", dort stehen. "as" dient doch nur dazu dem Objekt, also deinem "Pmw" Modul in diesem Fall, einen anderen Namen zu geben. Deshalb kann er auch ScrolledText nicht finden, da die nicht im "Pmw" Modul verfügbar ist.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
hpr
User
Beiträge: 8
Registriert: Donnerstag 7. April 2011, 09:05

@Xynon1,
du irrst,

in deiner Variante tritt der Fehler auf.
In Pmp ist sehr wohl ScrolledText definiert.
Nach meiner Korrektur siehe DIFF funktioniert dein Script.
Leider aber mit dem Speicherproblem
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Dann stellt sich mir folgende Fragen, welche Python Version und welche Tk/Tcl Version hast du?
Und stimmt hast recht Pwm hat ein ScrolledText.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
hpr
User
Beiträge: 8
Registriert: Donnerstag 7. April 2011, 09:05

python 2.6.6 (r266:84292, Dec 26 2010, 22:31:48)
[GCC 4.4.5]

also Debian Sqeeze Stable

tcl8.5
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Dann sollte das Modul eigentlich dabei sein. Kannst ja mal nachschauen ob es physisch existiert, ansonsten nimm mal zum Test einfach nur "tkinter.Text".
Es ging mir einfach nur darum "pmw" als Fehlerquelle auszuschließen. Sonst wirst du dich wohl an BlackJacks Vorschlag halten müssen.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
hpr
User
Beiträge: 8
Registriert: Donnerstag 7. April 2011, 09:05

1.) der Fehler mit "Import ScrolledText as tktext" und dann "tktext.ScrolledText(..)" "module has no attribute" ist bei mit jetzt nicht mehr vorhanden. Habe auch auf anderem Rechner getestet funktioniert auch dort (bei Squeeze mit Speicherfraß). Das Schreiben in größeren blöcken brigt echt Performance.

2.) Mein Speicherproblem schein wirklich ein Tk/Tcl Problem zu sein. Squezze_amd64 und Suse11.3 haben es beide.

3.) Das Problem mit den neuen Versionen hat mich vom eigenlichen Problem abgebracht. Werde mein Programm jetzt unter Lenny debuggen.
hpr
User
Beiträge: 8
Registriert: Donnerstag 7. April 2011, 09:05

1. ein Klassiker es gab im Verzeichnis eine Datei ScrolledText.py vom testen. hat sich also erledigt

2. Habe das Beispiel mal in wish ausprobiert. Kein Problem in Lenny oder Squeeze.
Jetzt das Script von Xynon1 in
valgrind --leak-check=full --show-reachable=yes python py.py
ausgeführt. definitely lost: schlägt bei Lenny sofort zu. Es reicht schon

Code: Alles auswählen

from Tkinter import *
root= Tk()
root.title('Toplevel'
Label(root,text='This is the Toplevel').pack(pady=10)
root.mainloop()
Muss die detaillierten Ausschriften aber erst noch auswerten.

3. Bin mit dem Einbau von Queue für die Kommunikation Tkinter <-> Thread fast fertig. Funktioniert hat es auch ohne, aber eben mit Speicherklau.
Antworten