WX unter Linux ca um den Faktor 10 langsamer

Plattformunabhängige GUIs mit wxWidgets.
Antworten
freze
User
Beiträge: 8
Registriert: Montag 14. Februar 2005, 22:02

Hallo,

Als ich gestern mehrere tausend Werte in ein ListControl eintragen lies stellte ich etwas seltsames fest. Unter Windows dauerte das ganze etwa eine Sekunde ( bei 1500 Werten) - inkl. Gauge fuer die Statusanzeige. Unter Linux (Ubuntu 5.1 - Gnome) dauert es mindestens 10 Sekunden fuer die gleiche Arbeit.

Ist die Performance von WX unter Linux allgemein schlechter, ist dies nur bei Gnome oder bei bestimmten Controls so oder gibt es andere Erklaerungen fuer dieses Phaenomen?

Beim Code habe ich mich relativ nah an der Wx-Demo orientiert, daran duerfte es also eigentlich nicht liegen

Gruß
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

freze hat geschrieben:Ist die Performance von WX unter Linux allgemein schlechter, ist dies nur bei Gnome oder bei bestimmten Controls so oder gibt es andere Erklaerungen fuer dieses Phaenomen?
Hi freze!

Ich habe soeben unter Gnome eine Liste mit 10.000 Einträgen gefüllt. Das Programm, wenn es sich nach 30 sec. endlich wieder gefangen hat, ist kaum bedienbar.

Auch bei 3000 ist die Geschwindigkeit nicht unbedingt super. Das wird wohl am GTK, das unter Linux von WX verwendet wird, liegen.

Listengröße schrumpfen. Was anderes fällt mir nicht ein.

mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Nein, am GTK liegt es nicht. Ich habe eben einen Test mit GTK gemacht.

Eine Liste mit 10.000 Einträgen ist für GTK kein Problem. Es muss an etwas anderem liegen.

Wenn es jemanden interessiert. Diesen Code habe ich zum Testen verwendet:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import pygtk
pygtk.require("2.0")
import gtk

#----------------------------------------------------------------------
class SimpleList(gtk.ScrolledWindow):
    """
    Stellt eine einfache Liste mit Laufbalken dar. Das wird mit
    den Objekten ScrolledWindow und TreeView erreicht.
    """

    #----------------------------------------------------------------------
    def append_item(self, value, key = ""):
        """
        Fügt der Liste Werte und wenn gewünscht, Schlüssel hinzu.
        """

        self.list_store.append([key, value])


    #----------------------------------------------------------------------
    def _on_row_activated(self, treeview, path, view_column, data = None):
        """
        Setzt den Wert von self.selected_items. Dieser Wert kann
        mit der Methode "get_selection_data" abgerufen werden.
        """

        iter = self.list_store.get_iter(path)
        
        if iter:
            self.selected_item = (
                path[0], # Position
                self.list_store.get_value(iter, 0), # Key
                self.list_store.get_value(iter, 1) # Value
            )


    #----------------------------------------------------------------------
    def _on_cursor_changed(self, widget, data1 = None, data2 = None):
        """
        Setzt den Wert von self.selected_items. Dieser Wert kann
        mit der Methode "get_selection_data" abgerufen werden.
        """

        selection = widget.get_selection()
        (model, iter) = selection.get_selected()

        if iter:
            self.selected_item = (
                int(selection.get_selected_rows()[1][0][0]), # Position
                str(model.get_value(iter, 0)), # Key
                str(model.get_value(iter, 1)) # Value
            )


    #----------------------------------------------------------------------
    def get_selection_data(self):
        """
        Gibt die Variable self.selected_item zurück.
        Diese enthält ein Tupel. (<Position>, <Key>, <Value>)
        """
        
        return self.selected_item  # (<Position>, <Key>, <Value>)
    

    #----------------------------------------------------------------------
    def set_eventfunction__cursor_changed(self, function):
        """
        Verbindet die übergebene Funktion mit dem 
        Signal "cursor-changed".
        """
        
        self.tree_view.connect("cursor-changed", function)


    #----------------------------------------------------------------------
    def __init__(self):
        """
        Initialisieren
        """
        
        gtk.ScrolledWindow.__init__(self)
        self.selected_item = None # (<Position>, <Key>, <Value>)
        
        # Liste
        self.list_store = gtk.ListStore(str, str)
        
        # ScrolledWindow
        self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        #self.set_border_width(6)
        self.set_shadow_type(gtk.SHADOW_IN)
        
        # Treeview
        self.tree_view = gtk.TreeView(self.list_store)
        self.tree_view.set_headers_visible(False)
        self.tree_view.get_selection().set_mode(gtk.SELECTION_BROWSE)
        self.tree_view.connect("cursor-changed", self._on_cursor_changed)
        self.tree_view.connect("row-activated", self._on_row_activated)
        self.tree_view.show()
        
        # Key-Spalte hinzufügen
        self.key_cell = gtk.CellRendererText()
        self.key_column = gtk.TreeViewColumn("Key")
        self.key_column.pack_start(self.key_cell, True)
        self.key_column.add_attribute(self.key_cell, "text", 0)
        self.key_column.set_visible(False)
        self.tree_view.append_column(self.key_column)
        
        # Value-Spalte hinzufügen
        self.value_cell = gtk.CellRendererText()
        self.value_column = gtk.TreeViewColumn("Caption")
        self.value_column.pack_start(self.value_cell, True)
        self.value_column.add_attribute(self.value_cell, "text", 1)
        self.tree_view.append_column(self.value_column)

        # Suchspalte setzen
        # Leider funktioniert die Suche im Moment nicht so 
        # wie ich das möchte. Deshalb habe ich die Suche abgeschaltet.
        self.tree_view.set_enable_search(False)
        #self.tree_view.set_search_column(1)
       
        # Anzeigen
        self.add(self.tree_view)
        self.show()
    
    
if __name__ == "__main__":

    # Fenster erstellen
    window = gtk.Window(gtk.WINDOW_TOPLEVEL)
    window.set_title("SimpleList")
    window.set_default_size(300, 400)
    window.connect("destroy", gtk.main_quit)
    
    # SimpleList erstellen und dazugehöriges Event verbinden
    simple_list = SimpleList()
    
    def cursor_changed(*args):
        print "(pos, key, value) =", simple_list.get_selection_data()
    
    simple_list.set_eventfunction__cursor_changed(cursor_changed)
    
    # SimpleList befüllen
    for item in [ "Hallo Welt %s" % item for item in range(10000) ]:
        simple_list.append_item(item)
    
    # Anzeigen
    window.add(simple_list)
    window.show()
    gtk.main()
mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

PS: Es gibt wahrscheinlich eine Möglichkeit, die Liste virtuell im Speicher zu halten und immer nur die aktuellen Einträge anzuzeigen. Ich suche mal -- vielleicht finde ich etwas.

mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
rayo
User
Beiträge: 773
Registriert: Mittwoch 5. November 2003, 18:06
Wohnort: Schweiz
Kontaktdaten:

Hi

Ja das gibt es und nennt sich VirtualList

Hier das Beispiel aus dem Buch wxPython in Action:

Code: Alles auswählen

import wx
import sys, glob, random
import data

class DataSource:
    def GetColumnHeaders(self):
        return data.columns
    def GetCount(self):
        return len(data.rows)
    def GetItem(self, index):
        return data.rows[index]
    def UpdateCache(self, start, end):
        pass

class VirtualListCtrl(wx.ListCtrl):
    def __init__(self, parent, dataSource):
        wx.ListCtrl.__init__(self, parent, \
                             style=wx.LC_REPORT|wx.LC_SINGLE_SEL|wx.LC_VIRTUAL)
        self.dataSource = dataSource
        self.Bind(wx.EVT_LIST_CACHE_HINT, self.DoCacheItems)
        self.SetItemCount(dataSource.GetCount())
        columns = dataSource.GetColumnHeaders()
        for col, text in enumerate(columns):
            self.InsertColumn(col, text)
    def DoCacheItems(self, evt):
        self.dataSource.UpdateCache(evt.GetCacheFrom(), evt.GetCacheTo())
    def OnGetItemText(self, item, col):
        data = self.dataSource.GetItem(item)
        return data[col]
    def OnGetItemAttr(self, item):
        return None
    def OnGetItemImage(self, item):
        return -1

class DemoFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, -1, "Virtual wx.ListCtrl", size=(600,400))
        self.list = VirtualListCtrl(self, DataSource())

app = wx.PySimpleApp()
frame = DemoFrame()
frame.Show()
app.MainLoop()
Der Hauptunterschied ist der Style (wx.LC_VIRTUAL).

Habs nicht getestet und im Buch steht, dass data einfach eine Klasse ist, die die Daten bereitstellt, mehr nicht.

Gruss

PS: falls es nicht geht kann ich dem Interessierten den Ausschnitt des Buches zuschicken, sind nur 5 Seiten
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Hi!

Angeregt von rayo's Beispiel, habe ich mich ein wenig umgesehen und bin auf die Objekte **VListBox** und **HtmlListBox** gestoßen. Zuerst habe ich VListBox ausprobiert, da der Name recht vielversprechend war. Aber die Handhabung war mir zu kompliziert. Dann habe ich mir noch HtmlListBox angesehen. Trotz des Namens, denke ich mal, dass **HtmlListBox** genau das richtige ist, um schnelle Listen zu erstellen.

Der einzige, optische Unterschied zur ListBox, den ich entdecken konnte: Zu breite Einträge werden in die nächste Zeile umgebrochen. Es erscheint keine horizontale Bildlaufleiste.

Hier ein Beispiel:

Code: Alles auswählen

import wx


class FastListBox(wx.HtmlListBox):

    _data = []
    
    def get_data(self):
        return self._data
    
    def set_data(self, data):
        self._data = list(data)
        self.SetItemCount(len(data))
    
    data = property(get_data, set_data)

    def OnGetItem(self, n):
        return self._data[n]


app = wx.App()
frame = wx.Frame(None, -1, "Hallo Welt")

choices = [ "Hallo Welt %s" % item for item in range(10000) ]

def on_listbox(event):
    print choices[event.GetSelection()]

fast = FastListBox(frame)
fast.Bind(wx.EVT_LISTBOX, on_listbox)
fast.data = choices
fast.SetFocus()

frame.Show()
app.MainLoop()
mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
rayo
User
Beiträge: 773
Registriert: Mittwoch 5. November 2003, 18:06
Wohnort: Schweiz
Kontaktdaten:

Hi

Also das versteh ich jetzt nicht warum du noch andere Objekte gesucht hast, das ListCtrl macht ja genau das.

Hier noch ein Beispiel der class DataSource, einfach mit der von meinem Beispiel ersetzen und laufenlassen, unter Win ist alles kein Problem (schnell gefüllt und Geschwindigkeit ist auch super:

Code: Alles auswählen

class DataSource:
    def __init__(self):
        self.data = [(str(x),str(x),'test','noch mehr eintraege') for x in xrange(10000)]
    def GetColumnHeaders(self):
        return ['x','y','teststring','str']
    def GetCount(self):
        return len(self.data)
    def GetItem(self, index):
        return self.data[index]
    def UpdateCache(self, start, end):
        pass
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

rayo hat geschrieben:Also das versteh ich jetzt nicht warum du noch andere Objekte gesucht hast, das ListCtrl macht ja genau das.
Hi rayo!

Ich wollte nur einen schnellen Ersatz für **ListBox** finden. Da bin ich auf **VListBox** gestoßen.

Ich wollte nur mehr über wxPython lernen. Mir ging es nicht darum, einen Ersatz für **ListCtrl** zu finden.

**HtmlListBox** ist mir ins Auge gestoßen, weil man damit nur die Methode **OnGetItem** überschreiben muss. Es ist also viel einfacher zu handhaben als **ListCtrl** mit dem Flag **wx.C_VIRTUAL** und die Handhabung von **VListBox** ist auch nicht unbedingt einfach.

Deshalb habe ich als Ersatz für die einfache **ListBox** **HtmlListBox** vorgeschlagen. Was natürlich kein Ersatz für das **ListCtrl** sein kann.

lg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
Antworten