2009-05-08 11 views
12

私は最近、PyQtを使っているプロジェクトで壁を打った。 QTreeViewはQAbstractItemModelに接続されています.QAbstractItemModelには通常、数千のノードがあります。これまでのところ、問題なく動作しましたが、今日、多くのノードを選択することは非常に遅いことに気付きました。いくつかの調査の後、QAbstractItemModel.parent()はあまりにも頻繁に呼び出されます。ちょうど(プロファイリングん)コードを実行すると、ツリーウィジェット内のすべてのノード(いずれかのシフト選択またはCMD-Aによる)を選択し、問題を再現するには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() 

:私は、問題を再現する最小限のコードを作成しました。アプリを終了すると、プロファイリング統計は次のように表示されます:

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のノードに!誰でも手掛かりがあるのですか?あなたのツリービューのsetUniformRowHeights(true)を呼び出す

答えて

3

てみ:

また、C++ツールはQTラボからmodeltestと呼ばれるがあります。私はPyQt5にあなたの非常に良い例のコードを変換し、Qt5.2で実行されていましたし、数字は依然として不可解巨大すなわち、類似していることを確認することができます

https://wiki.qt.io/Model_Test

+0

ヒントのおかげで、残念ながら、それは役に立たなかった。それは親コールの数を減らしましたが、134kコールまでしか削減しませんでした。 Modeltestについては興味深いと思いますが、サードパーティのC++コンポーネントをPyQtにインポートする方法はわかりません(私はそれをgoogleに追加する必要があります)。しかし、いずれにせよ、このモデルは正しいと私は思っていますか? –

0

:しかしpythonのために何があるかどうか私はわかりませんコール数。ここでは、たとえば開始のレポートの上部、すべてを選択して1ページをスクロールして終了する例を示します。

 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)
カウントが実際には過剰ですが(説明はありませんが)、累積値はとても大きく。また、これらの関数は、より高速に実行するために再コード化することもできます。例えばindex()の中で、 "self.nodesでないならtrue"が本当ですか?同様に、parent()とcreateIndex()のカウントがほぼ同じであることに注意してください。そうすれば、index.isValid()は真になります(エンドノードが親ノードよりもずっと多いので合理的です)。そのケースを最初に処理するための再コーディングは、親()の時間をさらに短縮します。編集:2番目の考えでは、そのような最適化は、 "タイタニックのデッキチェアを並べ替える"です。

関連する問題