本文翻译自Qt官网文档:
http://doc.qt.io/qt-5/qtquick-performance.html
QtQml应用程序的性能考虑与建议
1、时间考虑
作为一名程序开发者,应该努力使渲染引擎的刷新率维持在60fps,也就是说在每帧之间大约有16ms,这段时间包括了基本图元在图形硬件上的描画。具体内容如下:
>尽可能的使用异步事件驱动来编程。
>使用工作者线程来处理重要的事情,比如说QML的WorkerScript类型就是起用了一个新的线程。
>不要手动重复事件循环。
>每帧的函数阻塞的时间不要超过几毫秒。
如果不注意上面提到的内容,就会导致跳帧,影响用户体验。
注意:QML与C++交互时,为了避免阻塞就去创建自己的QEventLoop或调用QCoreApplication::processEvents(),这虽说是一种常见的模式,但也是危险的,因为信号处理或绑定进入事件循环时,QML引擎会继续运行其它的绑定、动画、状态迁移等,这些动作就可能带来副作用,例如,破坏包含了事件循环的层级结构。
2、性能分析
最重要的建议:使用QtCreator软件提供的QML性能分析工具,以查看应用程序的时间花销,这样就可以把着重点放在实际存在的问题上,而不是那些潜在的问题,QtCreator文档提供了QML性能分析工具的用法,可参考如下网址:
http://doc.qt.io/qtcreator/creator-qml-performance-monitor.html
通过QML性能分析工具,查看高频度的绑定、时间花销较大的函数,以优化焦点问题、重写实现细节,相反不使用QML性能分析工具的话,就没有显著的性能提升效果。
3、JavaScript代码
QML是对JavaScript的扩展,所以在QML应用程序中经常有大量的JavaScript代码,例如函数与信号的动态参数类型或属性绑定表达式,这些通常都不是问题所在,反而由于QML引擎的优化,使得绑定比C++函数调用效率更高,但也要注意不必要事件的偶发性。
3.1绑定
绑定在QML中有两种类型:优化的和非优化的。绑定表达式越简单越好,QML引擎发挥了优化的绑定表达式的求值特性,使得简单的绑定表达式不用转换到纯JavaScript运行环境就可以求值。优化的绑定在求值时比复杂的非优化的绑定效率更高,前提是所有用到的类型信息在编译时刻都应该是知道的。最大化地优化绑定表达式应该避免以下事情:
>声明JavaScript中间变量。
>访问“var”类型的变量。
>调用JavaScript函数。
>用绑定表达式构建闭包或定义函数。
>在即时求值范围外访问属性。
>与其它属性绑定引起的副作用。
在运行QML应用程序时,可能要设置QML_COMPILER_STATS环境变量以打印与绑定相关的数据,当知道对象和属性的类型时,绑定速度是最快的,也就是说在绑定表达式查值过程中某些非最终状态的属性的类型可能会有变化,这样绑定速度就变慢了。即时求值范围包括以下内容:
>绑定表达式所在对象的属性。
>组件中的id。
>组件中的根元素id。
其它组件的对象id、属性,还有通过import导入的东西,是不在即时求值范围的,在这种情况下,绑定是不被优化的。需要注意的是,如果绑定没有被QML引擎优化,就会在纯JavaScript环境中求值,这时上面的几点建议就不再适用了。有时候在非常复杂的绑定中,缓存JavaScript中间变量的属性求值结果是有益的,这个在后面的内容中会有描述。
3.2类型转换
当访问QML类型的属性时,对应的JavaScript对象就会被创建,这个对象带有外部资源,包括基本的C++数据,这是使用JavaScript的一个主要花销,在大多数情况下这个花销很小,但有时这个花销就很大,例如用C++的QvariantMap类型的数据(用到了Q_PROPERTY)赋值给QML的variant类型的属性,尽管int、qreal、bool、QString、QUrl参数化的QList列表花销不大,但其它类型的列表花销就很大了,这个涉及到JavaScript数组的创建、类型添加及转换。在基本类型间转换的花销也可能不小,比如说string和url类型间的转换,应该
使用匹配最接近的类型,避免不必要的类型转换。
如果必须暴露QVariantMap类型给QML时,QML的属性类型应该是var而不是variant,通常情况下,也应该优先考虑var类型,这样就可以存储真正的JavaScript引用,而且variant类型在QtQuick2.0及以后的版本中已标记为废弃使用。
3.3属性解析
属性解析要花一点时间,在某些情况下,多次访问同一属性时,其结果被存储起来的话,就可避免重复性的不必要的工作。如下例子,有个要经常用到的代码块,在for循环中多次访问了rect对象的color属性,其实就是一个共同的属性绑定表达式。
- // bad.qml
- import QtQuick 2.3
- Item {
- width: 400
- height: 200
- Rectangle {
- id: rect
- anchors.fill: parent
- color: "blue"
- }
- function printValue(which, value) {
- console.log(which + " = " + value);
- }
- Component.onCompleted: {
- var t0 = new Date();
- for (var i = 0; i < 1000; ++i) {
- printValue("red", rect.color.r);
- printValue("green", rect.color.g);
- printValue("blue", rect.color.b);
- printValue("alpha", rect.color.a);
- }
- var t1 = new Date();
- console.log("Took: " + (t1.valueOf() - t0.valueOf()) + " milliseconds for 1000 iterations");
- }
- }
在for循环中保存相同的rect的color:
- // good.qml
- import QtQuick 2.3
- Item {
- width: 400
- height: 200
- Rectangle {
- id: rect
- anchors.fill: parent
- color: "blue"
- }
- function printValue(which, value) {
- console.log(which + " = " + value);
- }
- Component.onCompleted: {
- var t0 = new Date();
- for (var i = 0; i < 1000; ++i) {
- var rectColor = rect.color; // resolve the common base.
- printValue("red", rectColor.r);
- printValue("green", rectColor.g);
- printValue("blue", rectColor.b);
- printValue("alpha", rectColor.a);
- }
- var t1 = new Date();
- console.log("Took: " + (t1.valueOf() - t0.valueOf()) + " milliseconds for 1000 iterations");
- }
- }
一个简单的改变就可以显著提高性能,上面的代码还可以做进一步的修改,因为rect对象的color属性在for循环中没有变,所以可以把这个存储过程移到for循环外: