整合終端效能改進
2017年10月3日 Daniel Imms, @Tyriar
整合終端的渲染引擎已完全重寫,以效能為中心,面向即將釋出的 Visual Studio Code 1.17 版本。在此版本中,我們從基於 DOM 的渲染系統轉向使用 HTML canvas 元素。
DOM 渲染
令人驚訝的是,在為顯示靜態文件而設計的系統中,互動式終端的渲染是可能的。然而,隨著時間的推移,我們發現 DOM 提供的某些功能需要被覆蓋才能修復一些問題。
選擇:為了覆蓋終端用例,我們做了很多與 DOM 選擇系統對抗的工作。由於我們總是隻渲染 DOM 可見的內容,所以如果不重新實現選擇功能,您將無法選擇多頁內容。滾動也會導致選擇丟失。為了解決這些問題,我們添加了自定義的選擇邏輯。
字元錯位:由於許多等寬字型對於某些 Unicode 字元並非嚴格等寬,這可能導致出現下圖右側所示的情況。

解決此問題的一種方法是將所有 Unicode 字元包裝在固定寬度的 span 中,但這會增加渲染一幀所需的時間。
過度的垃圾回收:由於渲染終端所需的元素數量龐大,垃圾回收器需要頻繁清理記憶體,這常常會明顯地擠佔渲染時間。我們引入了一個物件池來嘗試解決此問題,允許 DOM 元素被回收利用。
效能:無論我們多麼努力地解決這些問題,效能始終會受到佈局引擎設定的硬性上限的限制。
繞過佈局引擎
在某些情況下,僅組合元素和進行佈局就可能花費超過一幀(16.6 毫秒)的時間,如果我們希望在終端中保持流暢的 60 幀每秒(FPS),這是不可接受的。解決方案是採用新的基於 canvas 的渲染引擎。
HTML <canvas> 元素允許使用 JavaScript API 繪製圖形和文字。
渲染層
使用多個稱為“渲染層”的 canvas 元素來簡化終端不同部分的渲染。
當前層按順序排列如下:
- 文字:背景顏色和前景文字,此層是不透明的。
- 選擇:滑鼠選擇。
- 連結:滑鼠懸停在連結上時的下劃線。
- 游標:終端的游標。
將這些部分分離成各自的小元件極大地簡化了繪圖過程。
只繪製變化的部分
新渲染器的重要部分在於它只繪製發生變化的部分。為此,我們維護了一個精簡的內部模型,其中包含關於單元格繪製狀態的最少量資訊。然後使用該狀態來快速檢查單元格在執行更耗時的繪製操作之前是否需要更改。對於文字層,此模型包括對字元、文字樣式、前景色和背景色的引用。
與以前的渲染引擎相比,以前的渲染引擎會移除 DOM 中的整行,重新構建並重新新增,即使沒有任何變化。

上圖中綠色的矩形表示重新繪製的區域。
紋理圖集
紋理圖集被用來進一步提高渲染速度。後臺有一個 ImageBitmap,其中包含所有 ASCII 字元,以最常見的樣式和預設背景色顯示。
在繪製這些樣式的文字時,會使用紋理圖集,而不是常規呼叫 CanvasRenderingContext2D.fillText。由於 `ImageBitmap` 位於 GPU 上,因此繪製速度得到了顯著提高。

強制跳過幀
由於 DOM 渲染速度的原因,有必要跳過額外的幀,以確保解析器有足夠的時間使用 CPU。雖然這使得命令的執行速度比以前更快,但大量透過終端流式傳輸的資料會導致幀率下降到 10 FPS 以下。
有了新渲染器,這個限制已被解除,您現在可以在終端中享受高達 60 FPS 的體驗。

結果
我們的基準測試表明,整合終端現在的渲染速度比以前快 5 到 45 倍,具體取決於情況。即使您沒有注意到響應速度和幀率的提高,更快的渲染也意味著更低的電池消耗!我們希望您喜歡這些效能改進,它們將在幾天內隨 VS Code 1.17 版本釋出,並可在 Insiders 構建版中立即進行測試。
您還可以檢視 原始 xterm.js pull request,其中添加了該功能,以獲取更詳細的瞭解。
編碼愉快!
Daniel Imms, VS Code 團隊成員 @Tyriar