12

我最近在一个使用 PyQt 的项目中遇到了困难。我有一个连接到 QAbstractItemModel 的 QTreeView,其中通常有数千个节点。到目前为止,它工作正常,但我今天意识到选择很多节点非常慢。经过一番挖掘,事实证明 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()

要重现该问题,只需运行代码(执行分析)并选择树小部件中的所有节点(通过 shift 选择或 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() 的频率:2k 节点为 136k 次!任何人都知道为什么?

4

2 回答 2

3

尝试调用setUniformRowHeights(true)您的树视图:

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

此外,还有一个来自 qt labs 的名为 modeltest 的 C++ 工具。我不确定是否有python的东西:

https://wiki.qt.io/Model_Test

于 2009-05-10T13:00:13.437 回答
0

我将您非常好的示例代码转换为 PyQt5 并在 Qt5.2 下运行,并且可以确认数字仍然相似,即莫名其妙的大量调用。例如,这里是报告的顶部开始,cmd-A 全选,滚动一页,退出:

   ncalls tottime percall cumtime percall filename:lineno(function)
        1 14.880 14.880 15.669 15.669 {内置方法exec_}
   196712 0.542 0.000 0.703 0.000 /Users/dcortes1/Desktop/scratch/treeview.py:36(父)
   185296 0.104 0.000 0.104 0.000 {内置方法 createIndex}
    20910 0.050 0.000 0.056 0.000 /Users/dcortes1/Desktop/scratch/treeview.py:54(数据)
   225252 0.036 0.000 0.036 0.000 {内置方法isValid}
   224110 0.034 0.000 0.034 0.000 {内置方法 internalPointer}
     7110 0.020 0.000 0.027 0.000 /Users/dcortes1/Desktop/scratch/treeview.py:28(索引)
虽然计数确实过多(我没有解释),但请注意 cumtime 值并没有那么大。还可以重新编码这些功能以更快地运行;例如在 index() 中,“如果不是 self.nodes”是真的吗?类似地,请注意 parent() 和 createIndex() 的计数几乎相同,因此 index.isValid() 通常为真(合理,因为端节点比父节点多得多)。重新编码以首先处理这种情况将进一步减少 parent() cumtime。编辑:再想一想,这种优化是“重新安排泰坦尼克号上的躺椅”。

于 2014-04-22T15:57:02.433 回答