2009-05-08 7 views
12

J'ai récemment frappé un mur dans un projet sur lequel je travaille qui utilise PyQt. J'ai un QTreeView branché à un QAbstractItemModel qui a généralement des milliers de nœuds dedans. Jusqu'à présent, cela fonctionne bien, mais j'ai réalisé aujourd'hui que sélectionner beaucoup de nœuds est très lent. Après quelques recherches, il s'avère que QAbstractItemModel.parent() est appelé trop souvent. J'ai créé un minimum de code pour reproduire le problème:Lente sélection dans QTreeView, pourquoi?

#!/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() 

Pour reproduire le problème, il suffit d'exécuter le code (qui ne profilage) et sélectionnez tous les nœuds du widget arbre (soit par la sélection de changement ou Cmd-A). Lorsque vous quittez l'application, les statistiques de profilage montreront quelque chose comme:

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} 

La partie bizarre dans ces données est la fréquence parent() est appelée: 136k fois pour 2k nœuds! Quelqu'un a une idée pourquoi?

Répondre

3

Essayez d'appeler setUniformRowHeights(true) pour votre vue arborescente:

En outre, il est un outil C++ appelé modeltest des laboratoires qt. Je ne sais pas s'il y a quelque chose pour python si:

https://wiki.qt.io/Model_Test

+0

Merci pour l'indice, mais malheureusement, cela n'a pas aidé. Il a réduit le nombre d'appels de parents, mais seulement à 134k appels. Comme pour Modeltest, il semble intéressant, mais je ne sais pas comment importer des composants C++ tiers dans PyQt (je vais devoir google up). Mais en tout cas, il me semble que ce modèle est correct, n'est-ce pas? –

0

Je converti votre code exemple très agréable à PyQt5 et couru sous Qt5.2 et je peux confirmer que les chiffres sont toujours similaires, à savoir inexplicablement énorme nombre d'appels. Voici par exemple la partie supérieure du rapport pour démarrer, cmd-A pour sélectionner tout, défiler une page, quitter:

 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)
Et tandis que les comptes sont vraiment excessifs (et je n'ai aucune explication), notez que les valeurs cumtime ne sont pas tellement gros. De plus, ces fonctions pourraient être recodées pour fonctionner plus rapidement; par exemple dans index(), est-ce que "sinon self.nodes" est vrai? De même, notez que les nombres pour parent() et createIndex() sont presque les mêmes, donc index.isValid() est vrai beaucoup plus souvent qu'autrement (raisonnable, car les noeuds finaux sont beaucoup plus nombreux que les noeuds parents). Le recodage pour traiter ce cas d'abord couperait le parent cumtime() plus loin. Edit: à la réflexion, de telles optimisations "réarrangent les chaises longues sur le titanic".

Questions connexes