[翻译] 深入解析QML引擎, 第3部分: 绑定类型

原文 QML Engine Internals, Part 3: Binding Types

译者注:这个解析QML引擎的文章共4篇,分析非常透彻,在国内几乎没有找到类似的分析,为了便于国内的QT/QML爱好者和工作者也能更好的学习和理解QML引擎,故将这个系列的4篇文章翻译过来。翻译并不是完全直译,有不足之处,请指正,谢谢!

———————————————————————————————————————————

上一篇 绑定(Bindings)   下一篇 自定义解析器

这篇博文是深入解析QML引擎系列博文的第三篇。在上一篇博文中,我们揭示了QML引擎中的绑定是如何运作的。在这篇文章中,我们将深入了解不同的绑定类型。某些内容是我在开发者日对话QtQuick Under the Hood中讲过的。除此之外,这篇博文中将涵盖一些新的内容。

简要回顾

在回顾之前,让我们快速地浏览一个简单的绑定:

图1 绑定例子

每一个像这样的绑定实际上是一个JavaScript函数,运行时由V8引擎执行。执行的结果就是函数的返回值,然后将它设置给文本属性。由于V8并不知道Qt对象和属性,当遇到一个对象(如parent)或一个属性(如width)时,它就请求QML中的上下文包裹类和对象包裹类去解析它们。当一个绑定被执行时,这些包裹类会记录那些被访问了的属性,可以自动将每个属性的改变信号(例如widthChanged())连接到一个可以重新执行绑定的槽函数。

现在我们已经重新温习了一遍绑定的工作原理,让我们趁热打铁,继续分析不同的绑定方式。

绑定方式

在上一篇文章中,我指出每一个绑定都被解析成一个QQmlBinding对象的实例。这其实是一个哄骗孩子的谎言。如果每一个绑定都由QQmlBinding表示,则开销会非常大。一个典型的QML应用,即使没有上千个绑定,至少也有成百个绑定,所以需要让每一个绑定更加轻量级。此外,当加载一个QML文件时,每一个绑定都是单独编译的。因此在加载过程中会多次调用V8编译器,给系统造成不小的开销。

QV8Bindings

为了解决QQmlBinding造成的开销问题,使用了另外一个绑定类,取了一个容易混淆的名字:QV8Bindings。QV8Bindings内部用了一个数组来存放QML文件中的所有绑定,绑定用更加轻量级的QV8Bindings::Binding结构体来表示。QML的开发者过去花了很大力气去减少这种结构的内存占用,他们甚至发现指针的最后两位因为对齐的关系而没有被使用。然后丧心病狂地利用这些空间去保存标志位,最终做到一个QV8Bindings::Binding只占用了64个字节。

QV8Bindings和QQmlBinding相比,有一个大优势是,所有绑定都是在一起编译的,所以只需要调用一次V8编译器。在QQmlCompiler ::completeComponentBuild()函数中,你会发现,在编译QML文件时,所有的绑定函数会组成一个大的JavaScript程序,并存储在QQmlCompiledData(用于包含QML文件中所有类型的编译数据)。当QML文件第一次实例化时,QV8Bindings::QV8Bindings()将对绑定程序进行编译,编译后保存在QQmlCompiledData中,然后将源代码丢弃。当再次实例化相同的QML文件时,QML引擎将直接使用QQmlCompiledData中已编译的绑定程序,并不需要再编译一次它们。然而QQmlBinding却不是这样,每次实例化QML文件都需要执行一次编译。

小结:因为QV8Bindings把QML文件中所有的绑定组织在一起,所以可以花费更少的内存,并只执行一次编译。

那为什么我们不抛弃QQmlBinding?这个类为什么还依旧存在呢?某些情况下,绑定是不可共享的,例如它们使用了闭包或者使用了eval()函数。在这种情况下,每个绑定函数需要不同的上下文。因此不能和具有相同上下文的其他绑定一起编译。因此在这种特殊情况下,将会使用QQmlBinding来表示绑定。当编译一个QML文件时,是由QQmlCompiler::completeComponentBuild()来判定采用哪种绑定方式。另外,SharedBindingTester会检测绑定应该用QV8Bindings,还是QQmlBinding。SharedBindingTester就是一个JS AST的访问者。如果你查看一下代码,你会发现SharedBindingTester也会测试哪些绑定是安全的,同时在QML文件初始化时避免多次执行绑定,源代码的提交信息做了最好的描述。

为了让QML代码更加的简洁,QQmlBinding和QV8Bindings::Binding都从QQmlAbstractBinding继承。

QV4Bindings

假如你看过一些QML引擎的代码,你很可能已注意到QV4Bindings类,这个类也是QQmlAbstractBinding的子类。它是另一个绑定类型吗?和什么有关呢?与QV8Bindings相同的是,它也是QML文件中所有绑定的集合。不同的是,QV4Bindings只保存所谓优化过的绑定,也有人错误和混淆地称之为编译过的绑定。有一些绑定是可以被优化的,它们会用QV4Bindings表示,有一些绑定不能被优化,它们会用QV8Bindings来表示。

那么这个优化是什么呢?QV4Bindings并不由V8引擎执行,它会被编译成字节码,通过一个字节码解析器执行。这个字节码编译器和解析器无法处理所有的JavaScript表达式,因为不可能提前编译所有的JavaScript。

但是为什么使用字节码呢?V8引擎会编译成机器码,难道不比一个字节码解析器快吗?结果证明它真的没有字节码解析器快,V8引擎执行绑定时,需要调用QML来解析对象和属性,这个处理需要很大的开销。另外当一个函数被多次调用时,V8引擎可能会在比较繁忙的情况下重新编译一个函数,以此做更多地优化。对于QML的情况而言,所有这些处理都会造成很大的开销,因为QML通常包含很多只有一句代码的绑定。这里有一个我为开发者日准备的基准测试结果。在测试中,我只是简单的让QML引擎执行一个绑定几百次。这是一个可以让V4引擎轻松处理的简单绑定。为了和V8引擎比较,设置环境变量QML_DISABLE_OPTIMIZER=1来完全禁用V4绑定。

图2 V4和V8绑定执行效率对比

如你所见,在这种特定情形下,V4字节码引擎的确比V8快多了。

从本质上说,V4就是一个寄存器机器。和CPU相同的是,它具有的寄存器,用来存储临时值。不同的是,它不会从内存加载和储存值——它从类的属性加载和储存值。设置环境变量QML_BINDINGS_DUMP=1,让我们看一个简单的绑定:

图3 简单绑定例子

其指令输出是:

图4 绑定执行指令

如你所见,属性width和height被加载到寄存器0和1中,然后这些寄存器乘起来,把结果保存在文本属性中(文本属性在QQuickText类中的属性编号是42)。FetchAndSubscribe指令不仅加载属性,也会监听它的改变信号,从而实现绑定的自动更新。从上文的"汇编"代码中,你还可以发现另一个优势:V4编译器是在编译时解析对象和属性,并将属性的索引保存在字节码中。所以在运行时,就可以直接通过索引访问属性,不用通过属性名字进行查找。而V8引擎则需要调用QML对象和上下文包装器来解析对象和属性,这当然会产生更多的开销。但是缺点是,V4引擎无法处理动态对象,例如那些通过setContextProperty()从C++导出的对象。如果绑定中含这种动态对象,则需要使用QV8Binding。

总结

归纳起来,有3个绑定类型,都是从QQmlAbstractBinding继承:

1. QV4Bindings::Binding

2. QV8Bindings::Binding

3. QQmlBinding

QV4Bindings是最快的,因为其使用了自定义的字节码引擎。QV8Bindings和QQmlBinding都是使用V8 JS引擎执行,但QV8Bindings将所有的绑定组织在一起,一次性编译,然而QQmlBindings会在每个QML组件实例化过程中一个一个地进行编译。

这有一个展示所有绑定类型的(没啥用的)例子:

图5 三种类型的绑定的例子

设置环境变量QML_COMPILER_DUMP=1,你会看到QML编译器使用了两次STORE_COMPILED_BINDING,一次STORE_V8_BINDING和一次STORE_BINDING。

STORE_BINDING为QQmlBinding,它用于font.pointSize,因为绑定使用了eval(),因此不可以被共享。

anchors.centerIn的绑定和文字都是V4绑定(STORE_COMPILED_BINDING指令,QV4Bindings:: Binding类)。

最后,font.wordSpacing是一个普通的QV8Bindings::Binding(STORE_V8_BINDING指令)。V4的字节码编译器和解析器应对三元运算符完全没有问题,但是求补运算尚未实现,所以QML编译器选择使用V8绑定。

在这个系列的下一篇博文中,我们将尝试自定义解析器。如果有什么疑问或者对QML应用和研究感兴趣的朋友,欢迎加入我们进行讨论(QQ群:280689979)。如需转载,无须我们授权,但需要注明原文链接(该文的链接),及原作者,谢谢!

上一篇 绑定(Bindings)  下一篇 自定义解析器

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342

推荐阅读更多精彩内容