2009-05-08 8 views
12

Ich habe kürzlich eine Wand in einem Projekt getroffen, an dem ich arbeite, das PyQt verwendet. Ich habe ein QTreeView an ein QAbstractItemModel angeschlossen, das typischerweise Tausende von Knoten enthält. Bisher funktioniert es gut, aber ich habe heute festgestellt, dass die Auswahl vieler Knoten sehr langsam ist. Nach einigen Grabungen stellt sich heraus, dass QAbstractItemModel.parent() viel zu oft aufgerufen wird. Ich habe minimalen Code, um das Problem zu reproduzieren:Langsame Auswahl in QTreeView, warum?

#!/usr/bin/env python 
import sys 
import cProfile 
import pstats 

from PyQt4.QtCore import Qt, QAbstractItemModel, QVariant, QModelIndex 
from PyQt4.QtGui import QApplication, QTreeView 

# 200 root nodes with 10 subnodes each 

class TreeNode(object): 
    def __init__(self, parent, row, text): 
     self.parent = parent 
     self.row = row 
     self.text = text 
     if parent is None: # root node, create subnodes 
      self.children = [TreeNode(self, i, unicode(i)) for i in range(10)] 
     else: 
      self.children = [] 

class TreeModel(QAbstractItemModel): 
    def __init__(self): 
     QAbstractItemModel.__init__(self) 
     self.nodes = [TreeNode(None, i, unicode(i)) for i in range(200)] 

    def index(self, row, column, parent): 
     if not self.nodes: 
      return QModelIndex() 
     if not parent.isValid(): 
      return self.createIndex(row, column, self.nodes[row]) 
     node = parent.internalPointer() 
     return self.createIndex(row, column, node.children[row]) 

    def parent(self, index): 
     if not index.isValid(): 
      return QModelIndex() 
     node = index.internalPointer() 
     if node.parent is None: 
      return QModelIndex() 
     else: 
      return self.createIndex(node.parent.row, 0, node.parent) 

    def columnCount(self, parent): 
     return 1 

    def rowCount(self, parent): 
     if not parent.isValid(): 
      return len(self.nodes) 
     node = parent.internalPointer() 
     return len(node.children) 

    def data(self, index, role): 
     if not index.isValid(): 
      return QVariant() 
     node = index.internalPointer() 
     if role == Qt.DisplayRole: 
      return QVariant(node.text) 
     return QVariant() 


app = QApplication(sys.argv) 
treemodel = TreeModel() 
treeview = QTreeView() 
treeview.setSelectionMode(QTreeView.ExtendedSelection) 
treeview.setSelectionBehavior(QTreeView.SelectRows) 
treeview.setModel(treemodel) 
treeview.expandAll() 
treeview.show() 
cProfile.run('app.exec_()', 'profdata') 
p = pstats.Stats('profdata') 
p.sort_stats('time').print_stats() 

das Problem zu reproduzieren, nur den Code ausführen (das nicht Profilierungs) und wählen Sie alle Knoten im Baum-Widget (entweder durch Verschiebung Auswahl oder Cmd-A). Wenn Sie die App beenden, werden die Profilierungs Statistiken zeigen, so etwas wie:

Fri May 8 20:04:26 2009 profdata 

     628377 function calls in 6.210 CPU seconds 

    Ordered by: internal time 

    ncalls tottime percall cumtime percall filename:lineno(function) 
     1 4.788 4.788 6.210 6.210 {built-in method exec_} 
    136585 0.861 0.000 1.182 0.000 /Users/hsoft/Desktop/slow_selection.py:34(parent) 
    142123 0.217 0.000 0.217 0.000 {built-in method createIndex} 
    17519 0.148 0.000 0.164 0.000 /Users/hsoft/Desktop/slow_selection.py:52(data) 
    162198 0.094 0.000 0.094 0.000 {built-in method isValid} 
    8000 0.055 0.000 0.076 0.000 /Users/hsoft/Desktop/slow_selection.py:26(index) 
    161357 0.047 0.000 0.047 0.000 {built-in method internalPointer} 
     94 0.000 0.000 0.000 0.000 /Users/hsoft/Desktop/slow_selection.py:46(rowCount) 
     404 0.000 0.000 0.000 0.000 /Users/hsoft/Desktop/slow_selection.py:43(columnCount) 
     94 0.000 0.000 0.000 0.000 {len} 
     1 0.000 0.000 6.210 6.210 <string>:1(<module>) 
     1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects} 

Die seltsamen an diesen Daten ist, wie oft parent() aufgerufen wird: 136k mal für 2k Knoten! Jeder hat eine Ahnung warum?

Antwort

3

Try setUniformRowHeights(true) für Ihre Baumansicht aufrufe:

Außerdem gibt es ein C++ Werkzeug modelt von qt-Labor genannt. Ich bin mir nicht sicher, ob es allerdings etwas für Python ist:

https://wiki.qt.io/Model_Test

+0

Danke für den Hinweis, aber leider hat es nicht geholfen. Es hat die Anzahl der Elternanrufe reduziert, aber nur auf 134k Anrufe. Wie für Modelltest, scheint es interessant, aber ich weiß nicht, wie Sie C++ - Komponenten von Drittanbietern in PyQt importieren (ich muss es googeln). Aber auf jeden Fall scheint mir dieses Modell korrekt zu sein, oder? –

0

ich Ihren sehr schönen Beispiel-Code zu PyQt5 umgewandelt und lief unter Qt5.2 und kann bestätigen, dass die Zahlen noch ähnlich sind, dh aus unerklärlichen Gründen riesig Anzahl der Anrufe. Hier ist zum Beispiel der obere Teil des Berichts für den Start, cmd-A um alles auszuwählen, blättern Sie eine Seite, quit:

 ncalls tottime percall cumtime percall filename:lineno(function) 
     1 14.880 14.880 15.669 15.669 {built-in method exec_} 
    196712 0.542 0.000 0.703 0.000 /Users/dcortes1/Desktop/scratch/treeview.py:36(parent) 
    185296 0.104 0.000 0.104 0.000 {built-in method createIndex} 
    20910 0.050 0.000 0.056 0.000 /Users/dcortes1/Desktop/scratch/treeview.py:54(data) 
    225252 0.036 0.000 0.036 0.000 {built-in method isValid} 
    224110 0.034 0.000 0.034 0.000 {built-in method internalPointer} 
    7110 0.020 0.000 0.027 0.000 /Users/dcortes1/Desktop/scratch/treeview.py:28(index)
Und während die Zählungen wirklich übermäßig sind (und ich keine Erklärung habe), beachten Sie die cumtime Werte nicht so groß. Auch diese Funktionen könnten neu programmiert werden, um schneller zu laufen. zum Beispiel in index(), ist "wenn nicht self.nodes" jemals wahr? Gleichermaßen beachten Sie, dass die Zählwerte für parent() und createIndex() fast identisch sind, daher ist index.isValid() häufig häufiger wahr (sinnvoll, da Endknoten viel zahlreicher sind als übergeordnete Knoten). Das Umcodieren, um zuerst diesen Fall zu behandeln, würde die elterliche() Zeit zusätzlich verkürzen. Edit: Auf den zweiten Gedanken, solche Optimierungen sind "Neuanordnung der Liegestühle auf dem Titanic".