引言
前段时间有同学咨询关于大数据量的Model刷新时卡顿的优化方案,通过评论留言的方式回答了一部分,但感觉不够全面。因为这个是之前项目重点解决的问题,处理的过程中收获颇多,这里就基于之前的项目经验进行完整的总结,希望对大家在处理类似问题的过程中能有所启发。
可能的问题点及优化方法
在解决问题之前,首先需要先确定问题的原因,因为卡顿这类问题,只是一种表现,可能是多个耗时操作共同导致的现象,需要逐个问题耐心解决,对于陈旧项目特别如此,这里建议使用VS的代码调试工具,定位到耗时的具体代码段,才能高效解决问题。以下从常见的耗时点入手,逐个分析。
初始化耗时过长
在主线程中进行View和Model的关联,同时进行Model的初始化,这个是常见的用法,但存在一个问题,如果Model本身的数据量非常大,会导致初始化过程非常久,从而阻塞住主线程。
如果发现是这个问题很好解决,通过将初始化Model的任务推至子线程即可,不过在此之前需要检查初始化的代码中是否存在非必要的耗时操作,确认不是代码逻辑问题,才能把初始化推到子线程里, 不能因为无效操作配空消耗资源。
QFuture<void> tmpFuture = QtConcurrent::run(m_treadPool, tmpModel, &SourceModelBase::initData);
子线程内初始化可以通过QThread、QtConcurrent实现,如上所示,方便快捷。需要注意的是View和Model的关联,要放在Model初始化完成之后进行,也就是Model从子线程回到主线程之后,关联上视图就意味者需要操作UI,虽然模型和视图是通过信号槽链接,跨线程操作也没有问题,但是在初始化过程中大量的添加操作并没有必要体现在UI上,大量的信号会造成堆积,同样造成主线程阻塞。
更新item耗时过长
完成模型的初始化之后,我们要做的就是维护模型,需要进行增量刷新而不是全量属性,如item的添加、删除和修改,这些都是基于item进行操作的,而不是将模型原有数据全部清空之后重新生成。
由于是针对item进行更新,查找则是这一操作中最耗时的一环,只要找到需要更新的item,后续的修改、删除耗时基本上可以忽略不记。应对该问题最方便的方式就是为所有的item增加唯一表示uuid,在添加如模型时为id和其对于的指针放在map中进行关联,这样能够快速找到需要更新的item,因为map的实现本身就是红黑树,时间复杂度是nlogn的。当然这个是需要服务器支持的,也就是客户端知道需要更新的是哪一项。
还有一种方式,这个是所有场景都支持的,那就是万能的分页,模型内存储的只是有限的数据,而不是全量的数据,减轻维护的压力。
显示耗时过长
一般来说这个问题很少,因为Qt的视图模型之后更新当前视口内的数据,也就是大量的不可见数据是不会进行绘制的。如果怀疑这个问题,可以在模型初始化完成后,且无数据刷新时,大幅度调整滚动条去确认。
m_treeView->setUniformRowHeights(true);
确认有该问题,首先将视图的统一行高开启,如上所示。如果还是没有缓解就需要检查视图、视图代理,确认其中是否有耗时操作。
模型过多
这个问题就更少见了,项目存在大量差别不大的模型,如几十、上百个大数据量模型,这种可以通过代理模型的方式,去减少模型的数量,可以参考我之前的文章《QSortFilterProxyModel的使用》