Медленный выбор в QTreeView, почему? - PullRequest
12 голосов
/ 08 мая 2009

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

#!/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}

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

Ответы [ 2 ]

3 голосов
/ 10 мая 2009

Попробуйте позвонить setUniformRowHeights(true) для вашего дерева:

https://doc.qt.io/qt-4.8/qtreeview.html#uniformRowHeights-prop

Кроме того, есть инструмент C ++ под названием modeltest от qt labs. Я не уверен, что есть что-то для python:

https://wiki.qt.io/Model_Test

0 голосов
/ 22 апреля 2014

Я преобразовал ваш очень хороший пример кода в 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" когда-либо верно? Точно так же обратите внимание, что счетчики parent () и createIndex () почти одинаковы, поэтому index.isValid () имеет значение true гораздо чаще, чем не так (разумно, поскольку конечных узлов гораздо больше, чем родительских узлов). Перезапись для обработки этого случая сперва сократит время ожидания parent (). Редактировать: подумав, такие оптимизации "переставляют шезлонги на Титанике".
...