2009-05-08 6 views
12

Recientemente me he topado con una pared en un proyecto en el que estoy trabajando que utiliza PyQt. Tengo un QTreeView conectado a un QAbstractItemModel que generalmente tiene miles de nodos en él. Hasta ahora, funciona bien, pero me di cuenta hoy de que seleccionar muchos nodos es muy lento. Después de algunas excavaciones, resulta que QAbstractItemModel.parent() se llama demasiado a menudo. Creé código mínimo para reproducir el problema:Selección lenta en QTreeView, ¿por qué?

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

Para reproducir el problema, basta con ejecutar el código (que perfilado) y seleccione todos los nodos en el widget de árbol (ya sea a través de selección de marcha o Cmd-A). Cuando sale de la aplicación, las estadísticas de perfiles mostrará algo como:

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 parte extraña en estos datos es la frecuencia con la matriz() se llama: 136K tiempos de 2k nodos! Alguien tiene una pista ¿por qué?

Respuesta

3

intentar llamar a setUniformRowHeights(true) para su vista en árbol:

Además, hay una herramienta C++ llama Modeltest de los laboratorios qt. No estoy seguro de si hay algo para pitón sin embargo:

https://wiki.qt.io/Model_Test

+0

Gracias por la pista, pero desafortunadamente no sirvió de nada. Redujo la cantidad de llamadas principales, pero solo a 134k llamadas. En cuanto a Modeltest, parece interesante, pero no sé cómo importar componentes C++ de terceros en PyQt (tendré que buscarlo en google). Pero, en cualquier caso, me parece que este modelo es correcto, ¿no? –

0

Convertí su muy buen ejemplo de código a PyQt5 y corrió bajo Qt5.2 y puedo confirmar que las cifras siguen siendo similares, es decir, inexplicablemente enorme números de llamadas. Aquí, por ejemplo, está la parte superior del informe para comenzar, cmd-A para seleccionar todo, desplazarse por una página, salir:

 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)
Y aunque los recuentos son realmente excesivos (y no tengo ninguna explicación), observe que los valores de las horas no son tan grande. También esas funciones podrían ser recodificadas para correr más rápido; por ejemplo, en index(), ¿es verdadero "if not self.nodes"? Del mismo modo, observe que los recuentos para parent() y createIndex() son casi los mismos, por lo tanto index.isValid() es cierto con mucha frecuencia (razonable, ya que los nodos finales son mucho más numerosos que los nodos padre). La recodificación para manejar ese caso primero reduciría aún más el tiempo de ejecución parent(). Edición: pensándolo bien, tales optimizaciones son "reorganizar las tumbonas en el titánico".

Cuestiones relacionadas