[翻譯] 深入解析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閱讀 229,963評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,348評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,083評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,706評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,442評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,802評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,795評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,983評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,542評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,287評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,486評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,030評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,710評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,116評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,412評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,224評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,462評論 2 378

推薦閱讀更多精彩內容