客户端启动耗时分析:基于Qt、C++的Windows应用程序

客户端的启动作为所有交互的开端,经常被拿来进行分析、比较和优化,其中讨论比较多的就是启动耗时。本文整理了一些基于Qt、C++的Windows客户端的启动耗时问题和分析经验。

1 背景

客户端启动影响用户体验。启动耗时变久经常是由于工程师本身经验不足,在新增需求、修复其它Bug过程中慢慢引入耗时上涨因素。

2 分析

2.1 耗时成因

参考抖音团队总结的客户端5大不合理耗时成因如下:
在这里插入图片描述
上图中的“资源加载”有别于读文件,主要是指Qt、C++代码里边创建UI对象(包括布局、弹簧、按钮、标签等控件),某些较复杂页面常常涉及上千行动态申请内存代码。

2.2 分析方法

比较原始的启动耗时分析方法有手动打点统计函数执行耗时,然后用二分法来定位真正耗时的代码逻辑,比较低效。基于Qt/C++的Windows客户端,可以借助Visual Studio内置诊断工具来分析启动耗时。

2.2.1 诊断工具

使用Visual Studio 2022启动诊断工具如下:
在这里插入图片描述

可以在CPU曲线中选择要分析的起止时间,然后在下方选择“CPU使用率”一栏,其中列出了所有函数名及其在该时间段内占用的CPU时间(1000个示例/秒)。函数占用CPU时间决定了该函数执行时长,点击列表中的某个函数,就可以一步步进入查看函数调用树及对应占用的CPU时间。这里我们点击main函数,就可以进入查看客户端启动过程中执行函数的CPU时间占用详情:
在这里插入图片描述
通过Visual Studio诊断工具,我们了解到了启动耗时的主要原因及对应函数,为后续优化提供了有力参考。

2.2.2 防劣化

随着客户端不断迭代,其启动耗时分析和优化,就并非一蹴而就的工作。理论上来说,客户端的启动耗时优化存在以下3个阶段:
在这里插入图片描述
针对劣化期,整理了3个方面的防劣化手段,时间关系,本文仅简单提下(后续博文再做调研整理):

  • 监控预警:线下Visual Stuido诊断工具,线上埋点上报、云端监测/预警(关注低分位数据、低端机场景)
  • 完善UI/代码规范
  • 自测化测试

2.3 启动路径

基于Qt、C++的Windows应用程序,没有启动专属的页面、模块或组件;因此,我这里给出了“启动路径”描述,为后续分析、优化界定一个范围。

2.3.1 起点

客户端本质上是一个进程(或一个主进程+多个子进程),进程的启动时间即实际上客户端启动的起点。对用户而言,可能会有多种启动客户端的方式,比如双击桌面图标、双击工具栏图标等,但这些触发方式我们不必关心。
从代码实现来说,既可以通过Win32 API去获取进程启动时间,也可以直接用main函数开始执行时间来近似(基本一致)。

2.3.2 终点

关于启动结束时间点(终点),和用户直观感知到的时间有所不同。一般像Qt、C++客户端会使用启动画面来缓解用户等待的紧张心理,如下:

QPixmap pixmap(":/xxx.png");
QSplashScreen screen(pixmap);
screen.show();
application.exec();

实际上,在启动画面显示过程中,客户端无法响应用户的任何操作(鼠标、键盘),除了资源管理器强制关闭客户端等系统行为。
Qt应用通常在qapplication调用exec之前不能进行任何用户交互,除QMessageBox等模式小部件(Dialog::exec本地事件循环);因此,我们可以将application.exec()开始执行时间作为客户端启动终点。

2.4 发现问题

通过上述理论、工具分析桌面客户端的启动耗时,整理出了一些存在的问题。

2.4.1 UI初始化太久

如下图函数调用树及其CPU时间占用情况可以看到,客户端启动耗时主要花在初始化UI对象,启动过程中初始化了大量体积较大、并非必须启动时加载的页面。
这些页面的初始化,之所以放在启动流程中,其实是因为页面本身初始化的耗时就比较久,比较长的需要1~2s。如果这些页面延迟到用户第一次使用时再初始化,那么会导致UI卡顿,这也是不可接受的。
进一步查看各UI页面初始化的函数调用树,发现可以优化的有QStackedWidget,大部分不可见但会初始化的页面都通过QStackedWidget放入主UI页面里,如下:
在这里插入图片描述
从Qt Designer自动生成的代码(太长,这里不多展示)中发现,QStackedWidget每个子页面都会一起初始化;而比较合理的方法,应该是每个子页面在需要显示时再初始化。
因此,这里想到一个可优化的思路就是,用一个延迟初始化的自定义控件替换QStackedWidget,时间关系,后续博文再详细展开这个自定义控件如何设计。

2.4.2 非首屏页

桌面客户端,不像手机App本身画面较小,有着鲜明的页面堆栈,自然也有比较明显的首屏页和非首屏页的区分。在桌面客户端的开发历程中,大家对哪些页面应该是首屏页、哪些页面不应该是首屏页,并没有明确的认知,自然而然的把能放到主页面的UI控件、子页面都放了,从而导致启动时需要初始化的页面体量越来越大。
如果整体上控制客户端的启动耗时,明确首屏页和非首屏页是必不可少的。

2.4.3 注销(退出异常)

Windows+Qt客户端将退出(清理)逻辑直接写在main函数的application.exe()之后,结果发现当用户通过Windows注销来退出所有应用程序时,退出(清理)逻辑未执行。
Windows上用户注销时,系统会在Qt关闭所有顶级窗口后终止进程,因此退出应用前未必会执行application.exe()后续逻辑。
退出(清理)逻辑连接到aboutToQuit信号,而非main函数中。

3 优化

优化的前提是性能无损、业务无损,具体的方法主要有以下几个方面:
在这里插入图片描述

3.1 正面优化

如上图所述,我们可以使用性能分析工具(Visual Studio 诊断工具)对函数耗时做一个排名,然后从高到低看哪些函数是不符合预期的,逐个解决。下面整理了一些比较常规的正面优化办法。

删除非必要逻辑

  • 减少不必要的布局、Widget嵌套:布局层次越深,构造耗时越久;debug下多1个layout耗时增加约0.4ms
  • 不要先创建Widget设置成hidden:需要了再创建
  • 删除非启动页必须逻辑
    在这里插入图片描述

从上图中可以看到,减少1个layout可以减少5行代码。

缩减资源大小

  • 图标无损压缩
  • 避免直接使用高帧率Gif(或其它格式)

逻辑合并

  • 页面/数据复用:相同页面,不要反复创建

避免主线程IO

  • 不要在主线程发起请求,包括请求服务器
  • 读/写较大的数据库

3.2 延迟加载

延迟到需要切页时初始化;另外,多级页面,可以按层级按需初始化。如前文所述,延迟初始化UI主要的工作就是如何改造/自定义StackedWidget。
如果UI页面出现像之前说的,其本身初始化耗时就较久,卡UI,那么就进一步拆小页面,直到耗时符合标准;抖音将主线程耗时5ms以上的方法均认为不符合预期,短期目标可以根据实际测试效果为准。

3.3 异步加载

Qt异步加载,主要思路是将页面显示和数据处理分离,然后通过信号槽,同步数据到UI主线程,如下:
在这里插入图片描述
具体示例可以参考链接:https://zhuanlan.zhihu.com/p/348164011

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值