百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

QML 性能优化建议(一)

bigegpt 2024-09-16 12:12 5 浏览

时间因素

开发程序时,必须尽可能实现一致的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.不要删除对象的属性

【领QT开发教程学习资料,点击下方链接莬费领取↓↓,先码住不迷路~】

点击这里:「链接」

相关推荐

最全的MySQL总结,助你向阿里“开炮”(面试题+笔记+思维图)

前言作为一名编程人员,对MySQL一定不会陌生,尤其是互联网行业,对MySQL的使用是比较多的。对于求职者来说,MySQL又是面试中一定会问到的重点,很多人拥有大厂梦,却因为MySQL败下阵来。实际上...

Redis数据库从入门到精通(redis数据库设计)

目录一、常见的非关系型数据库NOSQL分类二、了解Redis三、Redis的单节点安装教程四、Redis的常用命令1、Help帮助命令2、SET命令3、过期命令4、查找键命令5、操作键命令6、GET命...

netcore 急速接入第三方登录,不看后悔

新年新气象,趁着新年的喜庆,肝了十来天,终于发了第一版,希望大家喜欢。如果有不喜欢看文字的童鞋,可以直接看下面的地址体验一下:https://oauthlogin.net/前言此次带来得这个小项目是...

精选 30 个 C++ 面试题(含解析)(c++面试题和答案汇总)

大家好,我是柠檬哥,专注编程知识分享。欢迎关注@程序员柠檬橙,编程路上不迷路,私信发送以下关键字获取编程资源:发送1024打包下载10个G编程资源学习资料发送001获取阿里大神LeetCode...

Oracle 12c系列(一)|多租户容器数据库

作者杨禹航出品沃趣技术Oracle12.1发布至今已有多年,但国内Oracle12C的用户并不多,随着12.2在去年的发布,选择安装Oracle12c的客户量明显增加,在接下来的几年中,Or...

flutter系列之:UI layout简介(flutter-ui-nice)

简介对于一个前端框架来说,除了各个组件之外,最重要的就是将这些组件进行连接的布局了。布局的英文名叫做layout,就是用来描述如何将组件进行摆放的一个约束。在flutter中,基本上所有的对象都是wi...

Flutter 分页功能表格控件(flutter 列表)

老孟导读:前2天有读者问到是否有带分页功能的表格控件,今天分页功能的表格控件详细解析来来。PaginatedDataTablePaginatedDataTable是一个带分页功能的DataTable,...

Flutter | 使用BottomNavigationBar快速构建底部导航

平时我们在使用app时经常会看到底部导航栏,而在flutter中它的实现也较为简单.需要用到的组件:BottomNavigationBar导航栏的主体BottomNavigationBarI...

Android中的数据库和本地存储在Flutter中是怎样实现的

如何使用SharedPreferences?在Android中,你可以使用SharedPreferencesAPI来存储少量的键值对。在Flutter中,使用Shared_Pref...

Flet,一个Flutter应用的实用Python库!

▼Flet:用Python轻松构建跨平台应用!在纷繁复杂的Python框架中,Flet宛如一缕清风,为开发者带来极致的跨平台应用开发体验。它用最简单的Python代码,帮你实现移动端、桌面端...

flutter系列之:做一个图像滤镜(flutter photo)

简介很多时候,我们需要一些特效功能,比如给图片做个滤镜什么的,如果是h5页面,那么我们可以很容易的通过css滤镜来实现这个功能。那么如果在flutter中,如果要实现这样的滤镜功能应该怎么处理呢?一起...

flutter软件开发笔记20-flutter web开发

flutterweb开发优势比较多,采用统一的语言,就能开发不同类型的软件,在web开发中,特别是后台式软件中,相比传统的html5开发,更高效,有点像c++编程的方式,把web设计出来了。一...

Flutter实战-请求封装(五)之设置抓包Proxy

用了两年的flutter,有了一些心得,不虚头巴脑,只求实战有用,以供学习或使用flutter的小伙伴参考,学习尚浅,如有不正确的地方还望各路大神指正,以免误人子弟,在此拜谢~(原创不易,转发请标注来...

为什么不在 Flutter 中使用全局变量来管理状态

我相信没有人用全局变量来管理Flutter应用程序的状态。毫无疑问,我们的Flutter应用程序需要状态管理包或Flutter的基本小部件(例如InheritedWidget或St...

Flutter 攻略(Dart基本数据类型,变量 整理 2)

代码运行从main方法开始voidmain(){print("hellodart");}变量与常量var声明变量未初始化变量为nullvarc;//未初始化print(c)...