Подтвердить что ты не робот

Замедленный выбор в QTreeView, почему?

Я недавно ударил стену в проекте, над которым я работаю, и использует 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 узлов! Кто-нибудь знает, почему?

4b9b3361

Ответ 2

Я преобразовал ваш очень хороший примерный пример в 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(). Редактирование: во-вторых, такая оптимизация - "перестановка шезлонгов на титанике".