将QML编译为C++:避免duck类型

本文介绍了如何通过避免QML中的鸭子类型来优化性能,利用qmlsc将QML编译为C++。作者讨论了在编译时遇到的问题,如未声明的属性和潜在的类型阴影,并提供了解决方案,如使用QML_ELEMENT宏和Q_PROPERTY的FINAL属性。通过这些修改,可以减少绑定的执行时间和潜在的性能瓶颈。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Compiling QML to C++: Avoiding duck typing

将QML编译为C++:避免duck类型

Tuesday May 31, 2022 by Ulf Hermann | Comments

​2022年5月31日,星期二,乌尔夫·赫尔曼 评论

This is the fourth installment in the series of blog posts on how to adjust your QML application to make the most of qmlsc. In the first post we've set up the environment. You should read that post first in order to understand the others. In the second post I've shown how to add type annotations to JavaScript functions. In the third post I've shown how to navigate around various pitfalls you may find when making types visible at compile time.

​这是关于如何调整QML应用程序以充分利用qmlsc的博客文章系列的第四部分。在第一篇文章中,我们设置了环境。为了理解其他人,你应该先读那篇文章。在第二篇文章中,我展示了如何向JavaScript函数添加类型注释。在第三篇文章中,我展示了如何绕过在编译时使类型可见时可能发现的各种陷阱。

This time around I'll write about a type category qmlsc cannot handle. If it walks like a duck and quacks like a duck, it is a duck. This concept refers to the practice of assuming the presence of properties and methods on objects without them being explicitly declared. From the place where you tell the duck to walk you don't really care how it walks. It should just do. Furthermore, you might not even care if quack() returns a string or a foozle. In JavaScript you can introspect the result and dispatch on its type, or just pass it on as an opaque piece of data. This, however, is where the compilation to C++ breaks. If we want to make the duck quack, we want to know what type to construct in C++. Otherwise we'd have to use QVariant or a similarly generic type for all such things. That would be more expensive than what the QML engine does when interpreting byte code. Where is the duck in our example project? The first warning generated from the CategoryLabel.qml file is as follows:

​这次我将讨论qmlsc无法处理的类型类别。如果它走路像鸭子,嘎嘎叫像鸭子,那它就是鸭子。这个概念是指假定对象上存在属性和方法,而不显式声明它们的实现。从你告诉鸭子走路的地方开始,你根本不在乎它怎么走路。应该就这样。此外,您甚至可能不在乎quack()是否返回字符串或foozle。在JavaScript中,您可以内省结果并对其类型进行调度,也可以将其作为不透明的数据块传递。然而,这就是C++编译中断的地方。如果我们想让鸭子呱呱叫,我们想知道在C++中构造什么类型。否则,我们将不得不对所有这些事情使用QVariant或类似的泛型类型。这将比QML引擎在解释字节码时所做的更昂贵。在我们的示例项目中,鸭子在哪里?从CategoryLabel..qml文件生成的第一个警告,如下:

Warning: CategoryLabel.qml:36:41: Property "displayName" not found on type "QObject"
    property string text: model ? model.displayName : ""

We know that this particular QObject has a property displayName, because we know that it is in fact a TimelineModel as declared in src/libs/tracing/timelinemodel.h. However, qmlsc does not know. And it doesn't trust us. We could assign some object without a displayName to the "model" property tomorrow, after all.

我们知道这个特定的QObject有一个属性displayName,因为我们知道它实际上是一个在src/libs/tracing/TimelineModel中声明的TimelineModel.h。 然而,qmlsc不知道。而且它不信任我们。毕竟,明天我们可以将一些没有显示名的对象分配给“model”属性。

Let's check how expensive this binding was. Profile Qt Creator again, and have it open our example trace. You can click the label on line 36 in CategoryLabel.qml to see how long it took. On my machine, the binding was evaluated 24 times, with a cumulative duration of 53.3µs. Click the label again a few times and notice that the table in the profiler jumps between a different lines. If you take a look at the two subtables at the bottom, you will see that one of the lines in the "Callee" table refers to the same file and line has the line in the main table:

让我们看看这个绑定有多贵。再次配置Qt-Creator,并将其打开我们的示例跟踪。您可以单击CategoryLabel.qml中第36行的标签。来看看花了多长时间。在我的机器上,对绑定进行了24次评估,累计持续时间为53.3µs。再次单击标签几次,请注意探查器中的表格在不同的行之间跳转。如果查看底部的两个子表,您将看到“Callee”表中的一行引用了相同的文件,该行在主表中有一行:

 Here we have 40.4µs total time. How come? To clarify this, you can select the timeline view and zoom in using Ctrl and the mouse scroll wheel on the point you're interested in. As we are dealing with a very short time span you may need to scroll for while. Eventually you'll arrive at something like this:

这里我们有40.4µs的总时间。怎么会?为了澄清这一点,您可以选择时间线视图,并在感兴趣的点上使用Ctrl键和鼠标滚轮进行放大。由于我们处理的时间跨度很短,您可能需要滚动一段时间。最终你会得到这样的结果:

You can see that there are two bindings and two JavaScript functions at play here. The binding on CategoryLabel.text is implemented by the JavaScript expression on its right hand side and those are measured separately. The binding measurement includes the overhead of finding the right function to call and writing the result to the property. qmlsc is only concerned with compiling the JavaScript expression to C++, though. So, the JavaScript events are the more interesting ones for us.

您可以看到,这里有两个绑定和两个JavaScript函数。对CategoryLabel.text的绑定。文本由其右侧的JavaScript表达式实现,这些表达式是单独度量的。绑定度量包括查找要调用的正确函数以及将结果写入属性的开销。不过,qmlsc只关心将JavaScript表达式编译为C++。因此,JavaScript事件对我们来说更有趣。

TimelineModel so far is an anonymous type. We can see that from the QML_ANONYMOUS macro in its declaration. In order to show the compiler that there is a displayName to the "model" property, we can give TimelineModel a QML name, and use that as type of the property. Replace the QML_ANONYMOUS with QML_ELEMENT to use the "TimelineModel" name also in QML, and declare the model property in CategoryLabel.qml as follows:

迄今为止,TimelineModel是一种匿名类型。我们可以从QML_ANONYMOUS 宏的声明中看到这一点。为了向编译器显示“model”属性有一个displayName,我们可以给TimelineModel一个QML名称,并将其用作属性的类型。将QML_ANONYMOUS替换为QML_ELEMENT,以便在QML中也使用“TimelineModel”名称,并在CategoryLabel.qml中声明model属性,如下:

property TimelineModel model

This makes it find the property, but now we get another warning:

这使它能够找到属性,但现在我们得到另一个警告:

Warning: CategoryLabel.qml:36:41: Could not compile binding for text: Member displayName of Timeline::TimelineModel of ??::model with type Timeline::TimelineModel can be shadowed
    property string text: model ? model.displayName : ""

What is this? Shadowing of a property is the practice of declaring the same property, potentially with a different type in a derived class in C++. The QML engine will pick the most derived class when resolving the type of a property. We could therefore go and declare a type EvilTimelineModel that provides a displayName property, but not as a QString, but rather as an EvilFoozle. The compiler will not generate any code to deal with such things as that would slow down the very, very common case where this is not done. Instead, it forces you to declare that you won't do it. This is what the "FINAL" attribute to Q_PROPERTY is for. A FINAL property cannot be shadowed. The QML engine rejects types that shadow FINAL properties. So, add FINAL to the displayName property as follows:

​这是什么?属性的隐藏是一种声明相同属性的实践,可能在C++中的派生类中使用不同的类型。在解析属性类型时,QML引擎将选择派生最多的类。因此,我们可以去声明一个提供displayName属性的类型EvilTimelineModel,但不是作为QString,而是作为EvilFoozle。编译器不会生成任何代码来处理这样的事情,因为这会减慢非常非常常见的情况,如果不这样做的话。相反,它迫使你声明你不会这么做。这就是Q_PROPERTY的“FINAL”属性的作用。无法对FINAL属性进行阴影处理。QML引擎拒绝隐藏FINAL属性的类型。因此,将FINAL添加到displayName属性中,如下所示:

Q_PROPERTY(QString displayName READ displayName WRITE setDisplayName NOTIFY displayNameChanged FINAL)

While, you're at it, you might do the same to all other properties in all other classes, too. This indeed silences the warning. Did this improve the performance? The QML Profiler tells me that now it takes a cumulative 50.1µs for the 24 bindings, 33.7µs of which are for the JavaScript execution. This is in contrast to 53.3µs and 40.4µs before. This time the speedup is not as clear cut. However, by allowing such bindings to be compiled to C++, you can prepare for further optimizations qmlsc might do here in future versions of Qt. For example, it might generate code that compresses the two lookups of "model" into one. When compiling ahead of time, we can see that the whole binding is free of side effects and therefore the "model" property cannot change between its two uses.

尽管如此,您也可以对所有其他类中的所有其他属性执行相同的操作。这确实使警告变得沉默。这是否提高了性能?QML探查器告诉我,现在24个绑定累计需要50.1µs,其中33.7µs用于JavaScript执行。这与之前的53.3µs和40.4µs形成对比。这一次,加速效果并不明显。然而,通过允许将此类绑定编译到C++,您可以为qmlsc在Qt的未来版本中可能进行的进一步优化做好准备。例如,它可能会生成将“model”的两个查找压缩为一个的代码。在提前编译时,我们可以看到整个绑定没有副作用,因此“model”属性不能在两种用途之间更改。

Compatibility

兼容性

We depend on declarative QML type registration for the QML_ELEMENT macro. Declarative type registration has been around since Qt 5.15. The FINAL attribute to Q_PROPERTY has been around since Qt 4.6.

 ​我们依赖于QML_ELEMENT宏的声明性QML类型注册。声明式类型注册自Qt 5.15以来就一直存在。Q_PROPERTY的FINAL属性从Qt 4.6开始就存在了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值