原文 QML Engine Internals, Part 3: Binding Typeshtml
譯者注:這個解析QML引擎的文章共4篇,分析很是透徹,在國內幾乎沒有找到相似的分析,爲了便於國內的QT/QML愛好者和工做者也能更好的學習和理解QML引擎,故將這個系列的4篇文章翻譯過來。翻譯並非徹底直譯,有不足之處,請指正,謝謝!數組
———————————————————————————————————————————安全
這篇博文是深刻解析QML引擎系列博文的第三篇。在上一篇博文中,咱們揭示了QML引擎中的綁定是如何運做的。在這篇文章中,咱們將深刻了解不一樣的綁定類型。某些內容是我在開發者日對話QtQuick Under the Hood中講過的。除此以外,這篇博文中將涵蓋一些新的內容。函數
在回顧以前,讓咱們快速地瀏覽一個簡單的綁定:學習
每個像這樣的綁定其實是一個JavaScript函數,運行時由V8引擎執行。執行的結果就是函數的返回值,而後將它設置給文本屬性。因爲V8並不知道Qt對象和屬性,當遇到一個對象(如parent)或一個屬性(如width)時,它就請求QML中的上下文包裹類和對象包裹類去解析它們。當一個綁定被執行時,這些包裹類會記錄那些被訪問了的屬性,能夠自動將每一個屬性的改變信號(例如widthChanged())鏈接到一個能夠從新執行綁定的槽函數。測試
如今咱們已經從新溫習了一遍綁定的工做原理,讓咱們趁熱打鐵,繼續分析不一樣的綁定方式。優化
在上一篇文章中,我指出每個綁定都被解析成一個QQmlBinding對象的實例。這實際上是一個哄騙孩子的謊話。若是每個綁定都由QQmlBinding表示,則開銷會很是大。一個典型的QML應用,即便沒有上千個綁定,至少也有成百個綁定,因此須要讓每個綁定更加輕量級。此外,當加載一個QML文件時,每個綁定都是單獨編譯的。所以在加載過程當中會屢次調用V8編譯器,給系統形成不小的開銷。ui
爲了解決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繼承。
假如你看過一些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綁定。
如你所見,在這種特定情形下,V4字節碼引擎的確比V8快多了。
從本質上說,V4就是一個寄存器機器。和CPU相同的是,它具備的寄存器,用來存儲臨時值。不一樣的是,它不會從內存加載和儲存值——它從類的屬性加載和儲存值。設置環境變量QML_BINDINGS_DUMP=1,讓咱們看一個簡單的綁定:
其指令輸出是:
如你所見,屬性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組件實例化過程當中一個一個地進行編譯。
這有一個展現全部綁定類型的(沒啥用的)例子:
設置環境變量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)。如需轉載,無須咱們受權,但須要註明原文連接(該文的連接),及原做者,謝謝!