我最近在一个正在研究的项目中使用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()的频率:2k节点的136k次!任何人都知道为什么?
答案 0 :(得分:3)
尝试为树视图调用setUniformRowHeights(true)
:
https://doc.qt.io/qt-4.8/qtreeview.html#uniformRowHeights-prop
此外,还有一个来自qt labs的名为modeltest的C ++工具。我不确定是否有python的东西:
答案 1 :(得分: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()中,“if not self.nodes”是否真的如此?类似地,请注意parent()和createIndex()的计数几乎相同,因此index.isValid()通常是真的(合理的,因为终端节点比父节点多得多)。重新编码以首先处理该情况将进一步削减parent()cumtime。编辑:第二个想法,这样的优化是“重新安排泰坦尼克号的躺椅”。