点数:新Qt Quick编译器的性能优势

The Numbers: Performance benefits of the new Qt Quick Compiler

点数:新Qt Quick编译器的性能优势

Tuesday January 18, 2022 by Ulf Hermann | Comments

​2022年1月18日星期二,Ulf Hermann | 评论

In my previous post, the history and general architecture of the new Qt Quick Compiler technology was explained. As promised there, the performance numbers are presented in this post.

​在我之前的文章中,解释了新的Qt Quick编译器技术的历史和总体架构。正如在那里承诺的那样,性能数据在这篇文章中给出。

Along with qmlsc we've created a number of benchmarks to measure its impact. Note that classical JavaScript benchmarks like v8bench won't get us very far because those mainly exercise the aspects of JavaScript qmlsc does not intend to optimize.

与qmlsc一起,我们创建了许多基准来衡量其影响。请注意,像v8bench这样的经典JavaScript基准不会很适合我们,因为那些主要执行JavaScript方面的基准并不打算进行优化。

The following results are produced with three modes of operation. All of the measurements were done on the current dev branch at the time of writing, which should be close enough to what we will release as Qt 6.3. There can be differences, though.

以下结果由三种操作模式产生。在撰写本文时,所有的测量都是在当前的dev分支上完成的,这应该足够接近我们将发布的qt6.3。但也可能存在差异。

  1. In plain mode, a QML file is loaded from the host file system, circumventing any caching or pre-compilation. It has to be compiled at run time and only byte code is available. This byte code is then run through the JIT compiler.
  2. In qmlcachegen mode, qmlcachegen is utilized to compile the file ahead of time. qmlcachegen in Qt 6.3 will be able to compile to C++, in indirect mode. It cannot directly access your C++ classes, but has to utilize the lookup infrastructure in QtQml to access values.
  3. In qmlsc mode, qmlsc is utilized to compile the file ahead of time, with the --static and --direct options. Code generated by qmlsc in --direct mode does access your C++ classes directly, expecting to be able to #include their headers. --static means that it expects no properties to be shadowed. This is a slight deviation from the usual QML semantics, but allows for more bindings to be compiled.

1.在plain模式下,从主机文件系统加载QML文件,避免任何缓存或预编译。它必须在运行时编译,并且只有字节码可用。然后通过JIT编译器运行该字节码。

2.在qmlcachegen模式下,qmlcachegen用于提前编译文件。Qt 6.3中的qmlcachegen能编译成C++,在间接模式下编译。它不能直接访问C++类,但必须利用QtQml中的查找基础结构来访问值。

3.在qmlsc模式下,使用--static和--direct选项提前编译文件。在--direct模式下,qmlsc生成的代码直接访问C++类,希望能够包含它们的头。--static表示它不希望对任何属性进行阴影处理。这与通常的QML语义略有不同,但允许编译更多绑定。

As mentioned in the previous postqmlcachegen is available with all versions of Qt starting from Qt 5.8. Starting with Qt 6.3 alpha, it also provides the new compiler functionality and generates C++ code. qmlsc will implement "Qt Quick Compiler Extensions" as mentioned here. It is now available for commercial customers of "Qt for Device Creation" and "Qt for Device Creation Enterprise". The first Technology Preview release of qmlsc was already in Qt 6.2.1 in "Qt for Device Creation". "Qt Quick Compiler Extensions" (containing qmlsc) will also be available for general application development starting latest with the Qt 6.3.0 release. Further details around this will be announced some time soon.

​如前一篇文章所述,qmlcachegen可用于从qt5.8开始的所有Qt版本。从Qt 6.3 alpha开始,它还提供新的编译器功能并生成C++代码。qmlsc将实现这里提到的“Qt快速编译器扩展”。它现在可供“Qt for Device Creation”和“Qt for Device Creation Enterprise”的商业客户使用。qmlsc的第一个技术预览版本已经出现在Qt6.2.1的“Qt for Device Creation”中。“Qt Quick编译器扩展”(包含qmlsc)也可用于一般应用程序开发,最新版本为Qt 6.3.0。有关这方面的进一步细节将在不久的将来公布。

Real-world bindings

真实世界绑定

First, let's look at some examples of somewhat realistic bindings that exercise a number of value access patterns and arithmetics, adapted from the QmlBench project.

首先,让我们看一些实际绑定的示例,这些绑定使用了许多值访问模式和算法,这些模式和算法是从QmlBench项目改编而来的。

点数:新Qt Quick编译器的性能优势

We repeated trigger re-evaluation of a binding by manipulating one of its dependencies from C++ and then measure the time it takes to re-evaluate using QBENCHMARK. The bindings are as follows:

​我们重复触发损坏的绑定,通过从C++操纵其依赖关系,然后测量所需的时间,使用QBENCHMARK重新评估。绑定如下所示:​

bindings1

Item {
    id: root
    property int dynamicWidth: 10
    Rectangle { height: root.dynamicWidth + (5 * 3) - 8 + (root.dynamicWidth / 10) }
}

bindings2

Item {
    id: root
    property int dynamicWidth: 100
    property int dynamicHeight: rect1.height + rect2.height

    Rectangle {
        id: rect1
        width: root.dynamicWidth + 20
        height: width + (5 * 3) - 8 + (width / 9)
    }

    Rectangle {
        id: rect2
        width: rect1.width - 50
        height: width + (5 * 4) - 6 + (width / 3)
    }
}

bindings3

Item {
    id: root
    property int dynamicWidth: 10
    property int widthSignaledProperty: 20

    Rectangle { height: root.dynamicWidth + (5 * 3) - 8 + (root.dynamicWidth / 10) }
    onDynamicWidthChanged: widthSignaledProperty = dynamicWidth + (20 / 4) + 7 - 1
}

bindings4

Item {
    id: root
    property int dynamicWidth: 100
    property int dynamicHeight: rect1.height + rect2.height
    property int widthSignaledProperty: 10
    property int heightSignaledProperty: 10

    Rectangle {
        id: rect1
        width: root.dynamicWidth + 20
        height: width + (5 * 3) - 8 + (width / 9)
    }

    Rectangle {
        id: rect2
        width: rect1.width - 50
        height: width + (5 * 4) - 6 + (width / 3)
    }

    onDynamicWidthChanged: widthSignaledProperty = widthSignaledProperty + (6 * 5) - 2
    onDynamicHeightChanged: heightSignaledProperty = widthSignaledProperty + heightSignaledProperty + (5 * 3) - 7
}

We can see that all of those bindings exhibit a substantial speedup when compiled to C++, and another boost when compiled in direct mode with qmlsc.

我们可以看到,所有这些绑定在编译到C++时表现出相当大的加速,而在直接模式下用qmlsc编译时又会加速。

Micro benchmarks

微观基准

As noted above, those bindings are composed of a number of different operations. In the next step we examine some common operations in isolation in order to see where the AOT-compiled code saves most time. Let's first look at different kinds of value access.

如上所述,这些绑定由许多不同的操作组成。在下一步中,我们将单独检查一些常见操作,以查看AOT编译的代码在何处节省了最多时间。让我们首先看看不同类型的值访问。

点数:新Qt Quick编译器的性能优势

Each of these just profiles one binding a: b where the binding expression just reads one value. Here, we compare three types of access:

其中每一个都只配置了一个绑定a:b,其中绑定表达式只读取一个值。这里,我们比较三种访问类型:

  1. QProperty bindings are bindings on members of type QProperty, where the bindings expression accesses another QProperty. These are particularly easy to optimize for qmlsc because it only has to read the QProperty. The QProperty dependency mechanism takes care of the house keeping. For other bindings, we have to capture the property being read so that the dependency is registered.
  2. QML bindings are bindings where we cannot apply the QProperty optimization because one side of the binding is not a QProperty. Here we use the classical QML approach of capturing.
  3. Value type bindings are bindings to and from members of value types. These require some specialized lookups. The test here exercises two bindings, one that reads the x property of a member of type rect, and one that writes it.

1.QProperty绑定是QProperty类型的成员上的绑定,其中绑定表达式访问另一个QProperty。对于qmlsc来说,这些特别容易优化,因为它只需要读取QProperty。QProperty依赖机制负责内部管理。对于其他绑定,我们必须捕获正在读取的属性,以便注册依赖项。

2.QML绑定是无法应用QProperty优化的绑定,因为绑定的一侧不是QProperty。这里我们使用经典的QML捕获方法。

3.值类型绑定是与值类型成员之间的绑定。这些需要一些特殊的查找。这里的测试练习两个绑定,一个读取rect类型成员的x属性,另一个写入它。

Again, we can see a consistent performance increase when using qmlcachegen, and another one when using qmlsc.

同样,我们可以看到在使用qmlcachegen时,性能得到了一致的提高,而在使用qmlsc时,性能又得到了一致的提高。

Enumerations

枚举

Another area of interest is access to enumeration values. As enumeration values have to be qualified in QML, the QML engine has to do a lookup on the surrounding type of the enumeration in order to figure out which enumeration is meant. If we compile ahead of time, we can determine this statically. This is what you can see in the following result.

另一个感兴趣的领域是访问枚举值。由于枚举值必须在QML中限定,因此QML引擎必须查找枚举的周围类型,以便找出所指的枚举。如果我们提前编译,我们可以静态地确定这一点。这是您可以在下面的结果中看到的。

点数:新Qt Quick编译器的性能优势

Here, we repeatedly call a JavaScript function which returns an enumeration value. We do this once with an enumeration defined in C++ and once with an enumeration defined in QML. We see that the compiled code is a lot faster than the JIT.

这里,我们反复调用一个JavaScript函数,该函数返回一个枚举值。这一次我们用C++定义的枚举,一次用QML定义的枚举。我们看到编译后的代码比JIT快得多。

Storing values

存储值

Finally, we might also write values procedurally in QML. While you should avoid this where possible, we often see cases where it is not easily avoided. qmlcachegen and qmlsc can generate C++ code for such access. Here we exercise two different ways of storing a value:

最后,我们还可以按程序在QML中编写值。虽然您应该尽可能避免这种情况,但我们经常看到不容易避免的情况。qmlcachegenqmlsc可以生成用于这种访问的C++代码。在这里,我们使用两种不同的方法存储值:

  1. referencing the target object by id: onXChanged: root.y = x
  2. storing into the current scope: onInputChanged: output = input

1.按id引用目标对象:onXChanged: root.y = x

2.存储到当前作用域中:onInputChanged: output = input

点数:新Qt Quick编译器的性能优势

Again, we observe that the compiled code is faster than the JIT.

同样,我们观察到编译后的代码比JIT更快。

Conclusion

结论

These numbers are to be taken with a grain of salt. The code they are produced from is not finalized, yet. However, we can already see that the compilation of QML script code to C++ promises great performance benefits. We are always happy to hear about your experience with Qt. As these compilers are fairly new, there probably still are some bugs in them. Please do report those at our bug tracker when you find them. Further improvements to qmlsc and qmltc are planned for the upcoming Qt releases. Stay tuned.

​这些数字是可以一概而论的。它们产生的代码尚未最终确定。然而,我们已经可以看到,将QML脚本代码编译成C++会带来巨大的性能效益。我们总是很高兴听到您使用Qt的经验。由于这些编译器是相当新的,所以可能仍然存在一些bug。当你发现它们时,请务必向我们的bug追踪器报告。在即将发布的Qt版本中,计划对qmlsc和qmltc进行进一步的改进。敬请期待。

上一篇:关于Qt Quick中将Canvas保存为QImage类型


下一篇:word2vec原理