啟動 Ignition 解釋器
V8 和其他現代 JavaScript 引擎通過即時編譯 (JIT)將腳本快速編譯成原生機器碼然後立即執行來獲得速度。程式碼最初由基準編譯器快速生成非最佳化的機器碼進行編譯。在執行期間,編譯後的碼會被分析並且可選擇使用更高級的最佳化編譯器重新編譯以達到最佳效能。在 V8 中,此腳本執行流程涉及多種特殊情況和條件,需用複雜的機制在基準編譯器與兩個最佳化編譯器 Crankshaft 和 TurboFan 之間切換。
這種方法的一個問題(除了架構的複雜性外)是即時編譯得到的機器碼可能消耗大量記憶體,即使程式碼只執行一次。為了降低這種開銷,V8 團隊建立了一個新的 JavaScript 解釋器,稱為 Ignition,它可以取代 V8 的基準編譯器,以更少的記憶體開銷執行程式碼,並為更簡化的腳本執行流程鋪平道路。
使用 Ignition,V8 將 JavaScript 函數編譯為簡潔的位元碼,其大小僅相當於等效基準機器碼的 50% 到 25%。高效能解釋器執行這些位元碼,在真實網站上的執行速度接近由現有的 V8 基準編譯器生成的程式碼。
在 Chrome 53 中,Ignition 將為 RAM 較少(512 MB 或以下)的 Android 設備啟用,這些設備最需要節省記憶體。田野中的早期實驗結果顯示,Ignition 使每個 Chrome 標籤頁節省了大約 5% 的記憶體。
詳情
在建立 Ignition 的位元碼解釋器時,團隊考慮了多種潛在的實現方法。一個傳統的解釋器,使用 C++ 編寫,無法高效與 V8 產生的其餘代碼交互。另一種選擇是手動用彙編編寫解釋器,但考慮到 V8 支援九個架構,這將造成巨大的工程開銷。
因此,我們選擇了一種利用 TurboFan 優勢的方法。TurboFan 是我們的新最佳化編譯器,已經為與 V8 執行時和其他生成的代碼進行最佳交互調整。Ignition 解釋器使用 TurboFan 的低級、與架構無關的宏彙編指令為每個操作碼生成位元碼處理程式。TurboFan 將這些指令編譯為目標架構,完成底層指令選擇和機器寄存器分配。這導致了高度最佳化的解釋器代碼,能夠以低開銷方式執行位元碼指令並與 V8 虛擬機器的其餘部分交互,同時僅新增少量新增的機制至代碼庫。
Ignition 是一個寄存器機器,每個位元碼將其輸入與輸出指定為顯式的寄存器操作數,而不是像堆疊機器那樣將每個位元碼輸入與輸出放入隱式堆疊。特殊的累加器寄存器是許多位元碼的隱式輸入與輸出寄存器。通過避免指定具體的寄存器操作數,這減少了位元碼的大小。由於許多 JavaScript 表達式涉及左右順序執行的運算鏈,這些運算的臨時結果通常可保留在累加器中直至整個表達式的求值結束,將載入至和儲存至顯式寄存器的操作減至最小。
在生成位元碼的過程中,會經過一系列內聯最佳化階段。這些階段對位元碼流進行簡單分析,用更快的序列替換常見模式,移除一些冗餘操作,並將不必要的寄存器載入和傳輸減至最少。整體上,這些最佳化進一步減小了位元碼的大小並提升了效能。
有關 Ignition 實現的更多細節,請參考我們的 BlinkOn 演講:
未來
到目前為止,我們對於 Ignition 的關注點一直是減少 V8 的內存開銷。然而,將 Ignition 添加到我們的腳本執行管道開啟了許多未來的可能性。Ignition 管道被設計用於使我們能夠更智能地決策,何時執行和優化代碼以加快網頁加載速度,減少卡頓,並提升 V8 各組件之間的交互效率。
敬請關注 Ignition 和 V8 的未來進展。