原文?QML Engine Internals, Part 3: Binding Types
譯者注:這個解析QML引擎的文章共4篇瓜客,分析非常透徹隘马,在國內(nèi)幾乎沒有找到類似的分析哼绑,為了便于國內(nèi)的QT/QML愛好者和工作者也能更好的學習和理解QML引擎,故將這個系列的4篇文章翻譯過來。翻譯并不是完全直譯苔咪,有不足之處搀突,請指正刀闷,謝謝!
———————————————————————————————————————————
這篇博文是深入解析QML引擎系列博文的第三篇仰迁。在上一篇博文中甸昏,我們揭示了QML引擎中的綁定是如何運作的。在這篇文章中徐许,我們將深入了解不同的綁定類型施蜜。某些內(nèi)容是我在開發(fā)者日對話QtQuick Under the Hood中講過的。除此之外雌隅,這篇博文中將涵蓋一些新的內(nèi)容翻默。
簡要回顧
在回顧之前缸沃,讓我們快速地瀏覽一個簡單的綁定:
每一個像這樣的綁定實際上是一個JavaScript函數(shù),運行時由V8引擎執(zhí)行修械。執(zhí)行的結(jié)果就是函數(shù)的返回值趾牧,然后將它設置給文本屬性。由于V8并不知道Qt對象和屬性肯污,當遇到一個對象(如parent)或一個屬性(如width)時翘单,它就請求QML中的上下文包裹類和對象包裹類去解析它們。當一個綁定被執(zhí)行時蹦渣,這些包裹類會記錄那些被訪問了的屬性哄芜,可以自動將每個屬性的改變信號(例如widthChanged())連接到一個可以重新執(zhí)行綁定的槽函數(shù)。
現(xiàn)在我們已經(jīng)重新溫習了一遍綁定的工作原理柬唯,讓我們趁熱打鐵认臊,繼續(xù)分析不同的綁定方式。
綁定方式
在上一篇文章中锄奢,我指出每一個綁定都被解析成一個QQmlBinding對象的實例失晴。這其實是一個哄騙孩子的謊言。如果每一個綁定都由QQmlBinding表示斟薇,則開銷會非常大师坎。一個典型的QML應用,即使沒有上千個綁定堪滨,至少也有成百個綁定胯陋,所以需要讓每一個綁定更加輕量級。此外袱箱,當加載一個QML文件時遏乔,每一個綁定都是單獨編譯的。因此在加載過程中會多次調(diào)用V8編譯器发笔,給系統(tǒng)造成不小的開銷盟萨。
QV8Bindings
為了解決QQmlBinding造成的開銷問題,使用了另外一個綁定類了讨,取了一個容易混淆的名字:QV8Bindings捻激。QV8Bindings內(nèi)部用了一個數(shù)組來存放QML文件中的所有綁定,綁定用更加輕量級的QV8Bindings::Binding結(jié)構體來表示前计。QML的開發(fā)者過去花了很大力氣去減少這種結(jié)構的內(nèi)存占用胞谭,他們甚至發(fā)現(xiàn)指針的最后兩位因為對齊的關系而沒有被使用。然后喪心病狂地利用這些空間去保存標志位男杈,最終做到一個QV8Bindings::Binding只占用了64個字節(jié)丈屹。
QV8Bindings和QQmlBinding相比,有一個大優(yōu)勢是伶棒,所有綁定都是在一起編譯的旺垒,所以只需要調(diào)用一次V8編譯器彩库。在QQmlCompiler ::completeComponentBuild()函數(shù)中,你會發(fā)現(xiàn)先蒋,在編譯QML文件時骇钦,所有的綁定函數(shù)會組成一個大的JavaScript程序,并存儲在QQmlCompiledData(用于包含QML文件中所有類型的編譯數(shù)據(jù))竞漾。當QML文件第一次實例化時司忱,QV8Bindings::QV8Bindings()將對綁定程序進行編譯,編譯后保存在QQmlCompiledData中畴蹭,然后將源代碼丟棄。當再次實例化相同的QML文件時鳍烁,QML引擎將直接使用QQmlCompiledData中已編譯的綁定程序叨襟,并不需要再編譯一次它們。然而QQmlBinding卻不是這樣幔荒,每次實例化QML文件都需要執(zhí)行一次編譯糊闽。
小結(jié):因為QV8Bindings把QML文件中所有的綁定組織在一起,所以可以花費更少的內(nèi)存爹梁,并只執(zhí)行一次編譯右犹。
那為什么我們不拋棄QQmlBinding?這個類為什么還依舊存在呢姚垃?某些情況下念链,綁定是不可共享的,例如它們使用了閉包或者使用了eval()函數(shù)积糯。在這種情況下掂墓,每個綁定函數(shù)需要不同的上下文。因此不能和具有相同上下文的其他綁定一起編譯看成。因此在這種特殊情況下君编,將會使用QQmlBinding來表示綁定。當編譯一個QML文件時川慌,是由QQmlCompiler::completeComponentBuild()來判定采用哪種綁定方式吃嘿。另外,SharedBindingTester會檢測綁定應該用QV8Bindings梦重,還是QQmlBinding兑燥。SharedBindingTester就是一個JS AST的訪問者。如果你查看一下代碼忍饰,你會發(fā)現(xiàn)SharedBindingTester也會測試哪些綁定是安全的贪嫂,同時在QML文件初始化時避免多次執(zhí)行綁定,源代碼的提交信息做了最好的描述艾蓝。
為了讓QML代碼更加的簡潔力崇,QQmlBinding和QV8Bindings::Binding都從QQmlAbstractBinding繼承斗塘。
QV4Bindings
假如你看過一些QML引擎的代碼,你很可能已注意到QV4Bindings類亮靴,這個類也是QQmlAbstractBinding的子類馍盟。它是另一個綁定類型嗎?和什么有關呢?與QV8Bindings相同的是,它也是QML文件中所有綁定的集合茧吊。不同的是贞岭,QV4Bindings只保存所謂優(yōu)化過的綁定,也有人錯誤和混淆地稱之為編譯過的綁定搓侄。有一些綁定是可以被優(yōu)化的瞄桨,它們會用QV4Bindings表示,有一些綁定不能被優(yōu)化讶踪,它們會用QV8Bindings來表示芯侥。
那么這個優(yōu)化是什么呢?QV4Bindings并不由V8引擎執(zhí)行乳讥,它會被編譯成字節(jié)碼柱查,通過一個字節(jié)碼解析器執(zhí)行。這個字節(jié)碼編譯器和解析器無法處理所有的JavaScript表達式,因為不可能提前編譯所有的JavaScript云石。
但是為什么使用字節(jié)碼呢唉工?V8引擎會編譯成機器碼,難道不比一個字節(jié)碼解析器快嗎汹忠?結(jié)果證明它真的沒有字節(jié)碼解析器快淋硝,V8引擎執(zhí)行綁定時,需要調(diào)用QML來解析對象和屬性错维,這個處理需要很大的開銷奖地。另外當一個函數(shù)被多次調(diào)用時,V8引擎可能會在比較繁忙的情況下重新編譯一個函數(shù)赋焕,以此做更多地優(yōu)化参歹。對于QML的情況而言,所有這些處理都會造成很大的開銷隆判,因為QML通常包含很多只有一句代碼的綁定犬庇。這里有一個我為開發(fā)者日準備的基準測試結(jié)果。在測試中侨嘀,我只是簡單的讓QML引擎執(zhí)行一個綁定幾百次臭挽。這是一個可以讓V4引擎輕松處理的簡單綁定。為了和V8引擎比較咬腕,設置環(huán)境變量QML_DISABLE_OPTIMIZER=1來完全禁用V4綁定欢峰。
如你所見,在這種特定情形下,V4字節(jié)碼引擎的確比V8快多了纽帖。
從本質(zhì)上說宠漩,V4就是一個寄存器機器。和CPU相同的是懊直,它具有的寄存器扒吁,用來存儲臨時值。不同的是室囊,它不會從內(nèi)存加載和儲存值——它從類的屬性加載和儲存值雕崩。設置環(huán)境變量QML_BINDINGS_DUMP=1,讓我們看一個簡單的綁定:
其指令輸出是:
如你所見融撞,屬性width和height被加載到寄存器0和1中盼铁,然后這些寄存器乘起來,把結(jié)果保存在文本屬性中(文本屬性在QQuickText類中的屬性編號是42)尝偎。FetchAndSubscribe指令不僅加載屬性捉貌,也會監(jiān)聽它的改變信號,從而實現(xiàn)綁定的自動更新冬念。從上文的"匯編"代碼中,你還可以發(fā)現(xiàn)另一個優(yōu)勢:V4編譯器是在編譯時解析對象和屬性牧挣,并將屬性的索引保存在字節(jié)碼中急前。所以在運行時,就可以直接通過索引訪問屬性瀑构,不用通過屬性名字進行查找裆针。而V8引擎則需要調(diào)用QML對象和上下文包裝器來解析對象和屬性,這當然會產(chǎn)生更多的開銷寺晌。但是缺點是世吨,V4引擎無法處理動態(tài)對象,例如那些通過setContextProperty()從C++導出的對象呻征。如果綁定中含這種動態(tài)對象耘婚,則需要使用QV8Binding。
總結(jié)
歸納起來陆赋,有3個綁定類型沐祷,都是從QQmlAbstractBinding繼承:
1. QV4Bindings::Binding
2. QV8Bindings::Binding
3. QQmlBinding
QV4Bindings是最快的,因為其使用了自定義的字節(jié)碼引擎攒岛。QV8Bindings和QQmlBinding都是使用V8 JS引擎執(zhí)行赖临,但QV8Bindings將所有的綁定組織在一起,一次性編譯灾锯,然而QQmlBindings會在每個QML組件實例化過程中一個一個地進行編譯兢榨。
這有一個展示所有綁定類型的(沒啥用的)例子:
設置環(huán)境變量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的字節(jié)碼編譯器和解析器應對三元運算符完全沒有問題局蚀,但是求補運算尚未實現(xiàn)麦锯,所以QML編譯器選擇使用V8綁定。
在這個系列的下一篇博文中琅绅,我們將嘗試自定義解析器扶欣。如果有什么疑問或者對QML應用和研究感興趣的朋友裸违,歡迎加入我們進行討論(QQ群:280689979)唠雕。如需轉(zhuǎn)載,無須我們授權权均,但需要注明原文鏈接(該文的鏈接)澎羞,及原作者髓绽,謝謝!