QML 性能优化建议(一)

时间因素

开发程序时,必须尽可能实现一致的60帧/秒刷新率。60帧/秒意味着每帧之间大约有16毫秒可以进行处理,其中包括将绘图基元上传到图形硬件所需的处理。

那么,就需要注意以下几个重要的点:

1.尽可能使用异步,事件驱动编程
2.使用工作线程进行重要处理
3.永远不要手动控制事件循环
4.在阻塞函数中,每帧的花费不要超过几毫秒

如果不这样做,那么将会发生调整,影响用户体验。

注意:永远不应该使用的模式是创建自己的QEventLoop或调用QCoreApplication :: processEvents(),以避免在从QML调用的C ++代码块中阻塞。这样做非常危险,因为当在信号处理程序或绑定中输入事件循环时,QML引擎继续运行其他绑定,动画,转换等。然后这些绑定会导致副作用,例如,破坏包含整体层次结构事件循环。

剖析

最重要的提示是:使用Qt Creator附带的QML分析器。了解应用程序在何处花费时间将使您能够专注于实际存在的问题区域,而不是可能存在的问题区域。有关如何使用QML分析工具的更多信息,请参阅Qt creator 帮助文档。

如果不进行分析而直接去优化代码,可能效果并不会很明显,借助分析器将会更快的定位到消耗性能的模块,然后再进行重新设计,以便提高性能。

JavaScript代码

大多数QML应用程序将以动态函数、信号处理程序和属性绑定表达式的形式包含大量JavaScript代码。这通常不是问题,由于QML引擎中的一些优化,例如对绑定编译器所做的那些优化,它可以(在某些用例中)比调用C ++函数更快。但是,必须注意确保不会意外触发不必要的处理。

绑定

QML中有两种类型的绑定:优化绑定和非优化绑定。保持绑定表达式尽可能简单是一个好主意,因为QML引擎使用优化的绑定表达式求值程序,它可以评估简单的绑定表达式,而无需切换到完整的JavaScript执行环境。与更复杂(非优化)的绑定相比,这些优化的绑定的评估效率更高。优化绑定的基本要求是在编译时必须知道所访问的每个符号的类型信息。

绑定表达式时要避免的事情,以达到最大的优化:

1.声明中间JavaScript变量
2.访问“var”属性
3.调用JavaScript函数
4.构造闭包或在绑定表达式中定义函数
5.访问直接评估范围之外的属性
6.写作其他属性作为副作用

立即评估范围可以概括为它包含:

1.表达式范围对象的属性(对于绑定表达式,这是属性绑定所属的对象)
2.组件中任何对象的ID
3.组件中根项的属性

来自其他组件的对象和任何此类对象的属性,以及JavaScript导入中定义或包含的符号都不在直接评估范围内,因此不会优化访问任何这些对象的绑定。

类型转换

使用JavaScript的一个主要成本是,在大多数情况下,当访问QML类型的属性时,会创建一个包含底层C ++数据(或对它的引用)的外部资源的JavaScript对象。在大多数情况下,这是不会太影响性能,但在其他情况下,它可能相当消耗性能。比如是将C ++ QVariantMap Q_PROPERTY分配给QML“variant”属性。列表也可能是有损性能的,尽管(特定类型的序列的QList为int, qreal,布bool,QString,和QUrl)应该相对来说不会太影响, 其他列表类型可能会带来昂贵的转换成本(创建新的JavaScript数组,逐个添加新类型,从C ++类型实例到JavaScript值的每类型转换)。

在一些基本属性类型(例如“string”和“url”属性)之间转换也可能很影响性能。使用最接近的匹配属性类型将避免不必要的转换。

如果必须将QVariantMap公开给QML,请使用“var”属性而不是“variant”属性。一般来说,对于来自QtQuick 2.0及更新版本的每个用例,“property var”应该被认为优于“property variant” (注意“property variant”被标记为过时),因为它允许真正的JavaScript引用存储(可以减少某些表达式中所需的转换次数)。

解决属性

虽然在某些情况下可以缓存和重用查找结果,但如果可能的话,最好完全避免完成不必要的工作。

在下面的示例中,我们有一个经常运行的代码块(在这种情况下,它是显式循环的内容;但它可能是一个通常评估的绑定表达式,例如),在其中,我们解决了具有“rect”id及其“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");
    }
}

我们可以在块中只解析一次公共基数:

// 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");
    }
}

只需这一简单的改变就可以显着提高性能。请注意,上面的代码可以进一步改进(因为在循环处理期间查找的属性永远不会改变),通过将属性解析提升出循环,如下所示:

// better.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();
        var rectColor = rect.color; // resolve the common base outside the tight loop.
        for (var i = 0; i < 1000; ++i) {
            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");
    }
}

属性绑定

如果更改了引用的任何属性,则将重新评估属性绑定表达式。因此,绑定表达式应尽可能简单。

如果你有一个循环来进行某些处理,但只有处理的最终结果很重要,通常最好更新一个临时累加器,然后将其分配给需要更新的属性,而不是逐步更新属性本身,以避免在累积的中间阶段触发重新评估结合表达。

以下的例子说明了这一点:

// bad.qml
import QtQuick 2.3

Item {
    id: root
    width: 200
    height: 200
    property int accumulatedValue: 0

    Text {
        anchors.fill: parent
        text: root.accumulatedValue.toString()
        onTextChanged: console.log("text binding re-evaluated")
    }

    Component.onCompleted: {
        var someData = [ 1, 2, 3, 4, 5, 20 ];
        for (var i = 0; i < someData.length; ++i) {
            accumulatedValue = accumulatedValue + someData[i];
        }
    }
}

onCompleted处理程序中的循环导致“text”属性绑定被重新评估六次(然后导致依赖于文本值的任何其他属性绑定,以及onTextChanged信号处理程序,每次重新评估时间,并列出每次显示的文本)。在这种情况下,这显然是不必要的,因为我们只关心最终的值。
那么,以上代码可以改成这样:

// good.qml
import QtQuick 2.3

Item {
    id: root
    width: 200
    height: 200
    property int accumulatedValue: 0

    Text {
        anchors.fill: parent
        text: root.accumulatedValue.toString()
        onTextChanged: console.log("text binding re-evaluated")
    }

    Component.onCompleted: {
        var someData = [ 1, 2, 3, 4, 5, 20 ];
        var temp = accumulatedValue;
        for (var i = 0; i < someData.length; ++i) {
            temp = temp + someData[i];
        }
        accumulatedValue = temp;
    }
}

序列提示

如前所述,某些序列类型很快(例如,QList ,QList ,QList ,QList < QString >,QStringList和QList < QUrl >),而其他序列类型则要慢得多。除了尽可能使用这些类型而不是较慢类型之外,还需要注意一些其他与性能相关的语法以获得最佳性能。

首先,对于序列类型的两种不同的实现:一个是当序列是Q_PROPERTY一个的QObject的(我们称此为参考序列),另一个用于在序列从返回Q_INVOKABLE一个功能的QObject(我们将这称为复制序列)。

通过QMetaObject :: property()读取和写入引用序列,因此读取和写入QVariant。这意味着从JavaScript更改序列中任何元素的值将导致三个步骤发生:将从QObject读取完整序列(作为QVariant,但随后转换为正确类型的序列); 指定索引处的元素将在该序列中更改; 并且完整的序列将被写回QObject(作为QVariant)。

复制序列更简单,因为实际序列存储在JavaScript对象的资源数据中,因此不会发生读取/修改/写入循环(而是直接修改资源数据)。

因此,对参考序列的元素的写入将比写入复制序列的元素慢得多。实际上,写入N元素参考序列的单个元素与将N元素复制序列分配给该参考序列的成本相当大,因此通常最好修改临时复制序列,然后将结果分配给计算过程中的参考序列。

假设以下C ++类型存在,并且已经正常注册过:

class SequenceTypeExample : public QQuickItem
{
    Q_OBJECT
    Q_PROPERTY (QList<qreal> qrealListProperty READ qrealListProperty WRITE setQrealListProperty NOTIFY qrealListPropertyChanged)

public:
    SequenceTypeExample() : QQuickItem() { m_list << 1.1 << 2.2 << 3.3; }
    ~SequenceTypeExample() {}

    QList<qreal> qrealListProperty() const { return m_list; }
    void setQrealListProperty(const QList<qreal> &list) { m_list = list; emit qrealListPropertyChanged(); }

signals:
    void qrealListPropertyChanged();

private:
    QList<qreal> m_list;
};

以下示例在多次循环中写入引用序列的元素,从而导致性能下降:

// bad.qml
import QtQuick 2.3
import Qt.example 1.0

SequenceTypeExample {
    id: root
    width: 200
    height: 200

    Component.onCompleted: {
        var t0 = new Date();
        qrealListProperty.length = 100;
        for (var i = 0; i < 500; ++i) {
            for (var j = 0; j < 100; ++j) {
                qrealListProperty[j] = j;
            }
        }
        var t1 = new Date();
        console.log("elapsed: " + (t1.valueOf() - t0.valueOf()) + " milliseconds");
    }
}

由表达式引起的内部循环中的QObject属性读取和写入"qrealListProperty[j] = j"使得此代码非常不理想。相反,更好的一种方法是:

// good.qml
import QtQuick 2.3
import Qt.example 1.0

SequenceTypeExample {
    id: root
    width: 200
    height: 200

    Component.onCompleted: {
        var t0 = new Date();
        var someData = [1.1, 2.2, 3.3]
        someData.length = 100;
        for (var i = 0; i < 500; ++i) {
            for (var j = 0; j < 100; ++j) {
                someData[j] = j;
            }
            qrealListProperty = someData;
        }
        var t1 = new Date();
        console.log("elapsed: " + (t1.valueOf() - t0.valueOf()) + " milliseconds");
    }
}

其次,如果属性中的任何元素发生变化,则会发出属性的更改信号。如果你对序列属性中的特定元素有很多绑定,最好创建一个绑定到该元素的动态属性,并将该动态属性用作绑定表达式中的符号而不是sequence元素,因为它将只有在其值发生变化时才会重新评估绑定。

// bad.qml
import QtQuick 2.3
import Qt.example 1.0

SequenceTypeExample {
    id: root

    property int firstBinding: qrealListProperty[1] + 10;
    property int secondBinding: qrealListProperty[1] + 20;
    property int thirdBinding: qrealListProperty[1] + 30;

    Component.onCompleted: {
        var t0 = new Date();
        for (var i = 0; i < 1000; ++i) {
            qrealListProperty[2] = i;
        }
        var t1 = new Date();
        console.log("elapsed: " + (t1.valueOf() - t0.valueOf()) + " milliseconds");
    }
}

请注意,即使在循环中仅修改索引2处的元素,也会重新评估三个绑定,因为更改信号的粒度是整个属性已更改。因此,添加中间绑定有时可能是有益的:

// good.qml
import QtQuick 2.3
import Qt.example 1.0

SequenceTypeExample {
    id: root

    property int intermediateBinding: qrealListProperty[1]
    property int firstBinding: intermediateBinding + 10;
    property int secondBinding: intermediateBinding + 20;
    property int thirdBinding: intermediateBinding + 30;

    Component.onCompleted: {
        var t0 = new Date();
        for (var i = 0; i < 1000; ++i) {
            qrealListProperty[2] = i;
        }
        var t1 = new Date();
        console.log("elapsed: " + (t1.valueOf() - t0.valueOf()) + " milliseconds");
    }
}

在上面的示例中,每次仅重新评估中间绑定,从而导致显着的性能提升。

值类型提示

值类型属性(font,color,vector3d等)具有类似的QObject属性,并将通知语义更改为序列类型属性。因此,上面给出的序列提示也适用于值类型属性。虽然它们通常不是值类型的问题(因为值类型的子属性的数量通常远小于序列中元素的数量),所以重新评估的绑定数量的任何增加不必要地会对绩效产生负面影响。

其他JavaScript对象

不同的JavaScript引擎提供不同的优化。Qt Quick 2使用的JavaScript引擎针对对象实例化和属性查找进行了优化,但它提供的优化依赖于某些标准。如果你的应用程序不符合标准,则JavaScript引擎会回退到“慢速路径”模式,性能会更差。因此,请始终尽量确保您符合以下条件:

1.尽可能避免使用eval()
2.不要删除对象的属性

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

luoyayun361

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值