集成终端性能改进
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 的新型渲染引擎。
<canvas>
HTML 元素 允许使用 JavaScript API 绘制图形和文本。
渲染层
使用多个被称为“渲染层”的 canvas 元素来简化终端不同部分的渲染。
当前的图层,按顺序排列为
- 文本:背景色和前景色文本,此图层不透明。
- 选择:使用鼠标进行选择。
- 链接:鼠标悬停在链接上时的下划线。
- 光标:终端的光标。
将这些部分分离成各自的小组件极大地简化了绘图方式。
只绘制已更改的部分
新渲染器的一个重要部分是它只绘制**已更改**的部分。为此,它维护一个精简的内部模型,其中包含单元格绘制状态的最小信息量。然后,该状态用于在执行更昂贵的绘制操作之前,快速检查单元格何时需要更改。对于**文本**层,此模型包括字符、文本样式、前景色和背景色的引用。
这与之前的渲染引擎形成对比,后者会从 DOM 中移除整行,然后重新构建并重新添加,即使没有任何改变。
上图中绿色的矩形表示被重绘的区域。
纹理图集
纹理图集用于进一步提高渲染时间。幕后有一个 ImageBitmap
,其中包含默认背景色上所有常见样式中的 ASCII 字符。
当绘制这些风格的文本时,会使用纹理图集,而不是常规调用 CanvasRenderingContext2D.fillText
。由于 `ImageBitmap` 与 GPU 协同工作,绘制速度显著提高。
强制跳帧
由于 DOM 中的渲染速度,为了确保解析器有足够的 CPU 时间,有必要跳过额外的帧。虽然这使得命令运行速度比以前快,但通过终端传输大量数据会导致帧率降至 10 FPS 以下。
有了新的渲染器,这个限制已经被移除,你现在可以在终端中享受高达 60 FPS 的帧率。
结果
我们的基准测试结果显示,集成终端的渲染速度比以前快了大约 **5 到 45 倍**,具体取决于情况。即使您没有注意到响应速度和帧率的提升,更快的渲染也意味着更少的电池消耗!我们希望您喜欢这些性能改进,它们将在几天后随 VS Code 1.17 版本发布,目前已可在 Insider 构建版中进行测试。
您还可以查看原始 xterm.js 拉取请求,其中添加了该功能,以获取更详细的信息。
编码愉快!
Daniel Imms,VS Code 团队成员 @Tyriar