2009-05-08 16 views
12

Я недавно ударил стену в проекте, над которым я работаю, и использует PyQt. У меня есть QTreeView, подключенный к QAbstractItemModel, который обычно имеет тысячи узлов в нем. Пока это работает хорошо, но сегодня я понял, что выбор большого количества узлов происходит очень медленно. После некоторого копания получается, что QAbstractItemModel.parent() вызывается слишком часто. Я создал минимальный код, чтобы воспроизвести проблему:Медленный выбор в QTreeView, почему?

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

Чтобы воспроизвести проблему, просто запустите код (который не профилирование) и выбрать все узлы в дереве виджета (либо путем выбора сдвига или Cmd-A). При выходе из приложения, профилирования статистика покажет что-то вроде:

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} 

Странная часть в этих данных, как часто родитель() называется: 136k раз для 2k узлов! Кто-нибудь знает почему?

ответ

3

setUniformRowHeights(true) Try вызова для вашего просмотра дерева:

Кроме того, есть инструмент C++ называется modeltest из Qt лабораторий. Я не уверен, если есть что-то для питона, хотя:

https://wiki.qt.io/Model_Test

+0

Спасибо за подсказку, но, к сожалению, это не помогло. Это уменьшило количество родительских вызовов, но только до звонков 134 тыс. Что касается Modeltest, это кажется интересным, но я не знаю, как импортировать сторонние компоненты C++ в PyQt (мне нужно будет его обработать). Но в любом случае мне кажется, что эта модель правильная, не так ли? –

0

я преобразовал свой очень хороший пример кода для PyQt5 и побежал под Qt5.2 и может подтвердить, что цифры по-прежнему похожи, т.е. необъяснимо огромным количество вызовов. Вот, например, верхняя часть отчета для начала, cmd-A, чтобы выбрать все, прокрутите одну страницу, закройте:

 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)
И хотя подсчеты действительно чрезмерны (и у меня нет объяснений), обратите внимание, что значения cumtime не являются такой большой. Также эти функции могут быть перекодированы для ускорения работы; например, в index(), является ли «если не self.nodes» когда-либо истинным? Аналогично, обратите внимание, что counts для parent() и createIndex() почти одинаковы, поэтому index.isValid() выполняется гораздо чаще, чем нет (разумно, поскольку конечные узлы намного более многочисленны, чем родительские узлы). Перекодирование для обработки этого случая сначала приведет к сокращению cumtime parent(). Редактирование: при второй мысли, такая оптимизация «перестраивает шезлонги на титанике».