分析解决ItemView的性能瓶颈

最近bug和qpang都非常勤劳, 相比较而言就显得俺太懒惰了点。 其实俺是忙得晕头转向, 没时间构思什么好文章了! 今天就把我这两天研究的一个bug给分析一下, 希望对大家有用。

背景

起源是有人问到说用同样的方法添加item到Qt4的QTreeWidget和到Q3ListView相比要慢很多。 开始笔者觉得这两者根本是没有可比性的, 说QTreeWidget慢觉得很有情可缘, 从QAbstractItemView一层一层派生上来, 这个QTreeWidget毋庸置疑,应该是最慢的一个类。 不过仔细想想, 插入3000个数据在笔者电脑上要用将近30秒, 这个速度确实是有点慢的离谱了, 至少是“有很大的提高空间”, 很值得分析一下。

Test Case

提问者提供了一个设计十分奇怪的小程序, 首先定义了一个时间间隔为零的timer, 在timeout信号的槽里往QTreeWidget里添加数据, 这样做法大约需要30s的时间完成3k数据的添加。 这种做法初看起来觉得多此一举, 为什么不直接往treewidget里填数据呢。 经过笔者测试, 如果直接在一个大循环里添加3k的数据只消耗一眨眼的功夫, 看来这个timer很“有效”啊。 当然一般的推断, 仅仅是timer的超时是不会消耗如此多的时间的, 否则Qt的用户早就要闹翻天了。 仔细思考, 测试程序的架构和一个循环到底有什么不同呢? 这其中的奥妙就需要对Qt略有研究的人才能回答拉(偷笑一个 ^_^)…. 最主要的问题是在signal/slot处理的过程中系统的events也被处理了。 恩, 现在问题就比较明确了, 肯定是某些事件的处理过程太耗时。

分析问题

笔者大胆假设, 问题大概是出在paint事件上(其实不用假设了, 不是paint还能是啥呢), 不过还需要求证一下才能确认。 确认的方法是用eventfilter截获treewidget的所有event, 把type打印出来。 结果是三个事件频繁出现, 一是Timeout, 二是MetaCall, 三是Paint。 Timeout事件出自我们的timer, MetaCall是信号和槽处理过程的产物, 那么毋庸置疑Paint就是罪魁祸首拉。 不过解决这个问题有点困难, QTreeWidget的代码里没有发现蛛丝马迹, 追溯到它的父类QTreeView, 翻遍QTreeView的代码发现调用update的地方实在是太多了,  尝试在update处加debug信息输出算是一个方法, 而且基于4.6的代码确实很容易找到了引起问题的update代码。  但拿来最新的4.7的代码却发现这部分有比较大的改动, 原来造成问题的update已经被去掉了。 是不是说这个性能问题已经fix了呢? 其实不然, 经过笔者测试, 4.7下这个速度也快不到哪儿去! 看来还需要深入研究。

解决问题

在4.7下加debug信息的方法不足以诊断该问题了, 在QTreeWidget里找不到毛病, 其他的相关文件一个一个找也不是办法。 笔者想到或许可以尝试用profiling的工具, 结果很杯具, 尝试了gprof和callgrind都有点不得其门而入, 没有得到什么有用的信息, 还搞得很郁闷。 (谁对这些工具有研究么, 欢迎投稿啊~~) 后来想想, 还是用原始的gdb更好。 当然拉, 我们也不用这么原始, 用Creator的图形化调试工具我们可以很方便的查看变量和堆栈的信息, 特别是back trace是我们重点要查看的内容。

在QWidget::update函数打断点然后运行程序, 调到update的地方有很多, 得到的back trace要稍加分析, 去掉不相关的, 最终大概会归结到一两个特别像引起问题的地方, 如果还不确定就可以在代码里另加debug信息。 这个方法并没有笔者想象的那么费时间, 其实用不了几分钟就找到了问题所在。

小结

4.6.2用加debug信息的方法找到引起重绘性能问题的代码在:

src/gui/itemviews/qtreeview.cpp

2522         if (parentItem != -1)
2523             d->viewItems[parentItem].hasChildren = true;
2524         d->updateChildCount(parentItem, delta);
2525
2526         updateGeometries();
2527         viewport()->update();//bug here

4.7.x的重绘性能问题在:

src/gui/itemviews/qabstractitemview.cpp

1135 void QAbstractItemView::doItemsLayout()
1136 {
1137     Q_D(QAbstractItemView);
1138     d->interruptDelayedItemsLayout();
1139     updateGeometries();
1140     d->viewport->update();//bug here
1141 }

经过测试, 4.6里去掉此行会有些update的问题, 看来需要在代码里增加判断以去掉不必要的刷新。 可行的方案是去掉此行, 然后在程序里合适的位置手动加update; 而4.7去掉此行貌似没有影响程序, 比较安全。

性能的提高那不是一点点阿, 怎么折腾都值了!

Tags: , , , ,
This entry was posted on Thursday, May 27th, 2010 at 5:39 PM and is filed under C++, Linux技术, Qt技术. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

5 Responses to “分析解决ItemView的性能瓶颈”

  1. slickqt says:

    遇到此问题的人太多了,好像连qt的支持人员都说请直接用model view来解决,性能将有质的飞跃.

  2. ee.zsy says:

    Qt4新增了mvc模式,
    可以先在model添加好数据,
    再转给View显示,
    就可以避免刷新了。

  3. shiroki says:

    如果数据能一次加进去当然最好拉, 可惜我遇到的这个情况就是不能这样加。 所以才容易发现问题。 其实还是代码写的有毛病, 不然性能还是很好的。

  4. somebody says:

    QTembedded,ARM平台下,
    1、移动窗体时CPU占用率超高
    2、触摸笔在屏幕上随便划时CPU占用率超高

    第一点不难理解,第2点就费解了。

  5. shiroki says:

    不懂这个和blog文章有啥关系? 你是希望我来分析分析这个问题? 如果是问问题的话还是发bbs吧

Leave a Reply