集成终端性能改进
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