QML Profiler
2018年1月26日 vincent
对于一个程序的开发,性能优化是开发中的一个重要步骤。
我们肯定不希望开发出来的程序表现出卡顿,最好是处处流畅,丝滑般的体验。
对于C++程序,我们有很多方法可以做性能优化,例如Visual Studio Profiler。
而对于QML(QtQuick)程序,我们可以选择QML Profiler,这是QtCreator的一个功能。
那么QML Profiler是什么呢,官方的描述如下:
You can use the QML Profiler to find causes for typical performance problems in your applications, such as slowness and unresponsive, stuttering user interfaces. Typical causes include executing too much JavaScript in too few frames. All JavaScript must return before the GUI thread can proceed, and frames are delayed or dropped if the GUI thread is not ready.
也就是说,QML Profiler主要功能就是帮助我们去解决程序中典型的性能问题,说简单就是帮助我们做性能优化。
注意:这个性能优化,仅指QML这里,一般来说就是界面,可能还包含点界面逻辑代码(JS),而C++这块,QML Profiler几乎帮不上忙,最多是能给在QML中调用的槽函数记个耗时。
时间的考虑
作为一名程序开发者,应该努力使渲染引擎的刷新率维持在60fps,也就是说在每帧之间大约有16ms,这段时间包括了基本图元在图形硬件上的描画。具体内容如下:
- 尽可能的使用异步事件驱动来编程。
- 使用工作者线程来处理重要的事情,比如说QML的WorkerScript类型就是起用了一个新的线程。
- 不要手动重复事件循环。
- 每帧的函数阻塞的时间不要超过几毫秒。
++如果不注意上面提到的内容,就会导致跳帧,影响用户体验。++
注意:
QML 与 C++ 交互时,为了避免阻塞就去创建自己的
QEventLoop 或调用QCoreApplication::processEvents(),
这虽说是一种常见的模式,但也是危险的,因为信号处理
或绑定进入事件循环时,QML 引擎会继续运行其它的绑定、
动画、状态迁移等,这些动作就可能带来副作用,例如,
破坏包含了事件循环的层级结构。
性能分析
借助于QML Profiler,我们快速的了解程序运行中的主要情况和耗时细则(可以精确到微秒),其中包括但不限于:
- 图片缓存使用情况
- 渲染耗时
- 内存使用情况
- 输入事件
- 动画帧率
- 编译耗时
- 创建耗时
- 绑定耗时
- 信号处理耗时
- JS代码耗时
如何使用QML性能分析工具
QML Profiler的功能开放是从Qt5.7开始的,之前一直是企业版才有的,也就是花钱版。
使用步骤
- 打开Qt Creator
默认安装了Qt5.7或更高版本。 - 打开一个QML项目
选择debug模式:
- 启动QML Profiler
启动工具后,等待程序运行起来,并且运行一段时间。然后点击Stop按钮,停止QML Profiler。
时间轴视图
在这里,我们可以以时间轴角度,查看各个细节的耗时。时间轴的起点,就是QQmlApplication实例化的时间。我们可能看不到零点,因为在QQmlApplication被实例化到第一个元素被开始处理,时间可能会有其他的耗时。
在视图中,从左到右,就是QML Profiler从开始到停止的所有记录了。越小的块表示时间越短,反之越大的块,表示时间越长。这里的方块具有一定的嵌套关系,下面的方块对象隶属于上面的对象。比如说 Windows { } 里面还可能会有一个 Item { } 这样的嵌套关系。
- 详细信息查看
通过鼠标左键点击颜色区域即可查看详细信息,如下:
看下面这个例子:
我们可以看出这里Image创建时间消耗78.7ms,对应的代码文件是main.qml和行数37行。
- 根据事件类型展开
在左侧不同类型的事件中,我们可以点击那个展开按钮,这样我们就可以看到展开的详细数据,这样看数据对应关系时会更加的清楚,但是当数据很多的时候也会更加的凌乱,所以酌情使用。
- 缩放按钮
在左侧有一个放大镜,可以缩放视图的比例,这对于分析一段比较长的QML Profiler或者想看某一个细节点的数据会非常有用
详细介绍:
- Pixmap Cache
在QML中使用的Image,默认是开缓存的。而所有缓存的图片,都会在这里显示,包括用了多少像素的缓存,还包括了图片的加载耗时、文件名等信息。(没有缓存的图片也会显示,但不会记入到缓存的阶梯里)
- Scene Graph
这里显示渲染时各个阶段的耗时,如果我们发现程序的动画有卡顿,除了一些函数的阻塞导致的卡顿外,还可以分析一下渲染的耗时开销,看看是不是渲染的量太大导致的卡顿。
这里我们主要关注Render Render这个数据,这个数据表示将OpenGL数据发送到GPU的过程。看到一个Render Render的结束,基本表示这一帧已经结束渲染,并且即将显示出来了。
另外还有Glyph Upload这个数据,这个数据表示字形纹路上传。如果你的程序是嵌入式,并且有很多的字,那么Glyph Upload有可能会带来一定的性能开销。减少这个开销的方式基本就是减少字,比如说用图片(Image)代替文字(Text或者Label)。
- Memory Usage
显示内存使用情况,如果这里有大块的内存增长,看看是不是这里在初始化很多东西,或者是有很多不必要的组件被创建出来了。
- Input Events
显示用户输入事件,例如鼠标和键盘事件
- Debug Messages
显示调试输出的时间点,如果你需要对照Debug输出和对应的QML事件,那么这会很有帮助
- Animations
显示是否有动画在执行,以及动画的FPS,在多线程渲染时还会显示多线程的信息。如果我们发现FPS低于18,那么视觉上可能就会有明显的卡顿了。而30到60的FPS,一般就可以认为是流畅的。
- Compiling
显示编译的耗时。这里要说下,从Qt5.8开始,QtQuick引入了qmlc机制,让编译时间大幅度缩减,基本上是从几百毫秒,缩减到几十甚至十毫秒以内。之前在csdn发过文章讲这个,这里再放下链接:
- Creating
显示创建的耗时,一般也是启动优化的主要部分
- Binding
显示绑定的耗时
- Hangling Signal
显示信号处理的耗时
- Javascript
显示JS执行的耗时。如果在QML里调用了一个C++的槽,那么这里也会有计时,但是也只有槽函数的总耗时,C++那里的运行情况这里看不出来。
统计图视图
选择统计Statistic Tab如下:
在这里,我们可以看到每个细则,例如编译、创建、绑定、JavaScript或者信号处理的次数以及它们所消耗的时间。
除了在时间轴那里,通过肉眼观察,我们在这里,通过对百分比的排序,也可以迅速的看出哪个东西最耗时。
火焰视图
选择Flame Graph Tab。
在这里,我们可以看到更加简洁的QML和JS统计。其中也直观的告诉了我们一些嵌套关系。
综上,这是最基本的3个功能区,构成了QML Profiler。我们程序的性能分析,主要也围绕着这三点展开。
性能优化建议
如果程序有明显的加载慢问题,那么可以先去看创建,找大块,去延后加载或者异步加载。让首界面先显示出来。尤其是图片,图片的加载比较慢,尽量选择合适分辨率的图片,不要过大。对于不会再第一时间显示的东西,尽量不要在第一时间加载。
如果程序有明显卡顿问题,那么可以看渲染那里,是不是渲染的东西太多了,例如用了过多的clip。或者有很多在视觉上看不到的元素,例如xy为-1000这样的Item,没有被隐藏,这些Item照样会渲染,照样会有性能开销,对于这些元素可以将visible设置为false,直接影藏掉,这就不会有渲染耗时了。例外值得一提的是,对于有动画的场景,建议把每帧时间控制在16ms以内,以维持60FPS的流畅界面。
关于性能优化进一步的细节点,这里不展开,以后单独发文章讲,本文只讲QML Profiler的基础。更多关于QML Profiler的信息,可以前往官网查看:
Profiling QML Applications