VS Code 是如何优化启动性能的?
Posted Alibaba F2E
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了VS Code 是如何优化启动性能的?相关的知识,希望对你有一定的参考价值。
本文主要是对 CovalenceConf 2019: Visual Studio Code – The First Second 这次分享的介绍,CovalenceConf 是一个以 Electron 构建桌面软件为主题的技术会议,这也是 VS Code 团队为数不多的对外分享之一(质量较高),主要分享了 VS Code 是如何优化启动性能的。
开头的一些内容
-
VS Code 的指导原则之一是尽可能快的让用户可以进入编辑状态 -
启动速度优化并不复杂,但它是许许多多小改进的总和,没有银弹 -
Monaco Editor 最早是 2011 年底开始的一个实验项目,目的是构建一款在浏览器中运行的开发人员工具
关于启动性能优化
-
性能优化基本的法则 -
测量,测量,还是测量,并基于此建立一个基准线 (VS Code 使用 Performance API,并对整个启动过程中的关键节点打点) -
建立监控,针对每个版本的性能变化快速做出优化措施 -
用一台7年前(现在来说是9年前)的 ThinkPad 做测试,确保它能在1.8秒内启动 VS Code -
不要过多的专注于 Electron、V8 这些底层依赖,因为有一群聪明的人在不断的优化它们,专注于加载代码以及运行程序。 -
确保代码尽可能快的加载 -
使用 Rollup、Webpack 等构建工具将代码打包成单文件,这可以节省约 400ms -
压缩代码,可以节省约 100ms -
使用 V8 Cached Data 将一些模块代码编译成字节码文件(一种中间态),这可以节省约 400ms, VS Code 自己使用 AMD Loader 实现了这个缓存,也可以直接用 v8-compile-cache 这个包。 -
生命周期阶段(Lifecycle Phases),分先后顺序来做应该做的事?不要一股脑全部执行 -
梳理清楚所有关于启动阶段事情的优先级 -
保证资源管理器和编辑器初始化,然后再做其他不是非常重要的事 -
requestIdleCallback, 将不那么重要的工作放在浏览器空闲时间执行 -
参考 Idle Until Urgent 这篇文章 -
通过一些小技巧使得界面「体感上」较快 -
切换编辑器时,使用 MouseDown 来替代 MouseUp / Click 事件,先确保 Tab 很快的切换 -
打开耗时较大的文件时,首先将面包屑、状态栏等其他 UI 部分渲染出来,使得用户感觉 UI 反应很快 -
重复以上步骤
性能优先
就是最重要的一条准则,诚然相比老牌的 Sublime Text,VS Code 性能表现并不能称得上优秀,但相比之下已经完全是可以接受的水平了。
社区也有很多开源编辑器采用了前后端分离技术,也就是使用 Web 技术构建编辑器 UI 部分,而核心的 TextBuffer 都使用 Native 实现,这类编辑器甚至可以替换 UI 层的技术实现,例如使用我们常见的 Electron,又或是 QT 等桌面端技术,因为编辑器涉及面太广,这里暂时不再赘述。
黑魔法
的想法来看这次分享的,然而当我结合代码看了三遍分享后满屏都是四个字
-
按照优先级划分启动顺序,永远确保文件树和编辑器最快渲染出来,并且光标第一时间在编辑器内跳动(这意味着用户可以开始编辑文件了) -
测量监控性能数据,每个版本都收集尽可能多的数据来直观的表现性能 -
对于出现的性能瓶颈快速做出改进
perf
和
startup-perf
相关的 issue,并且这些 issue 都有人长期跟踪解决的。
LCP
、
FID
以及
TTI
。
还有另一项指标 FMP (First Meaningful Paint 首次有效渲染时间) 不是很推荐,因为它无法直观的识别页面的主体内容是否加载完成,例如某些网站会在有意义的内容渲染前展示一个全屏的 Loading 动画,这对用户来讲显然是没有任何意义的,而相比之下 LCP 更为纯粹,它只看页面主体内容、图像是否加载完成。
从点开图标到我可以输入文本需要多久?
VS Code 的答案是 1 秒 (热启动在 500 毫秒左右)。
console.time
还是新的
Performance API
,在关键的节点添加这些性能标记,通过大量的数据收集可以得到一个真实的性能指标。VS Code 选择了 Performance API ,这样更方便汇总上报数据。运行
Startup Performance
命令可以看到这些性能指标的耗时 (总耗时2s+, 实际上 TTI 是 977ms)。
关键节点
。VS Code 是基于 Electron ,除了常规的页面渲染之外,还有一包括等待 Electron App Ready、创建窗口、LoadURL 等耗时,这部分的性能有专业的团队来保障(Electron、V8),不需要关心太多。所以重点需要关心的是 UI 部分的呈现及可交互时间。
性能优化
时,总是逃不开几条金科玉律
-
减小包体积,包括对 html、CSS、javascript 代码的压缩等 -
减小 HTTP 请求,使用服务端 gzip 压缩 -
使用 Webpack、Rollup 等现代构建工具来抽离公共代码,Code Splitting 代码拆分等
V8 Code Cache
vm
模块也提供了对该 API 的
包装
。由于 VS Code 使用
AMD Loader
作为模块加载器,所以内置实现了
V8 Code Cache
。
v8-compile-cache
即可。
import 'v8-compile-cache';
Contribution
来注册的。在早期的版本中,这些贡献点会在启动时就全部一起进行注册,这直接导致编辑器的加载被阻塞,最直观的表现就是界面所有 UI 都已经渲染出来并且可操作时,编辑器内的文本还没有加载出来(它们可能很大)。
-
Starting
应用开始启动阶段,非常底层的依赖需要在该阶段实例化 -
Ready
核心服务已经实例化完成 -
Restored
编辑器、UI 状态已经恢复完成(前一次关闭时缓存的状态) -
Eventually
准备就绪,意味着编辑器完全可用
// src/vs/workbench/common/contributions.ts
start(accessor: ServicesAccessor): void {
const instantiationService = this.instantiationService = accessor.get(IInstantiationService);
const lifecycleService = this.lifecycleService = accessor.get(ILifecycleService);
[LifecyclePhase.Starting, LifecyclePhase.Ready, LifecyclePhase.Restored, LifecyclePhase.Eventually].forEach(phase => {
this.instantiateByPhase(instantiationService, lifecycleService, phase);
});
}
instantiateByPhase(instantiationService: IInstantiationService, lifecycleService: ILifecycleService, phase: LifecyclePhase): void {
// 当达到对应的阶段时直接实例化贡献点
if (lifecycleService.phase >= phase) {
this.doInstantiateByPhase(instantiationService, phase);
}
// 未达到对应阶段时一直等待
else {
lifecycleService.when(phase).then(() => this.doInstantiateByPhase(instantiationService, phase));
}
}
Johannes Rieken
所说,这并不是非常复杂的技术问题,而是以一种更聪明的方式来对启动过程重新排序。这样一来,整体的启动过程会更加的有序,对于一些不是那么重要的任务,将它们的优先级靠后一些,从而确保能在第一时间将编辑器呈现出来,使用户进入可以编辑的状态。
// busy busy busy busy
// busy busy busy busy
// busy busy busy busy
requestIdleCallback((dealline) => {
// idle idle idle idle
// idle idle idle idle
// idle idle idle idle
})
// busy busy busy busy
// busy busy busy busy
// busy busy busy busy
requestIdleCallback
是一个浏览器提供的 API,用于在 CPU 空闲时间执行一些任务。相比
setTimeout
,
requestIdleCallback
的执行时机由浏览器来控制,因为浏览器知道何时才是
空闲时间
。利用
requestIdleCallback
,可以将一些必要但不紧急的工作延后处理,例如常见的一些埋点上报逻辑,可能会在触发某些高频率的交互操作时执行,而如果将这些逻辑与事件处理放在一起,很容易影响操作体验。
requestIdleCallback(processPendingAnalyticsEvents, { timeout: 2000 });
一般来说应该将执行时机交还给浏览器,让浏览器自行决定何时调用回调,如果设置了超时时间,则可能因为执行顺序被打乱。
感觉更快
。例如在这个 Case 中,点击打开一个大文件(2.5m)时,先将编辑器 Tab 以及面包屑渲染出来。
package.json
时,文件内容还是另一个文件,而面包屑已经变成了
package.json
。使用这种小技巧,在 VS Code 中切换编辑器时,会令用户觉得「反应好快」。
不建议在所有点击事件触发的地方都使用 MouseDown 来代替 MouseUp,因为复杂的 UI 可能还需要处理如拖动等事件,这会让事件处理更加复杂。
References
-
CovalenceConf 2019: Visual Studio Code – The First Second -
https://www.youtube.com/watch?v=r0OeHRUCCb4&ab_channel=ElectronUserland -
Web Dev : https://web.dev/ -
Idle Until Urgent -
https://developers.google.com/web/updates/2015/08/using-requestidlecallback#guaranteeing_your_function_is_called -
Using requestIdleCallback -
https://developers.google.com/web/updates/2015/08/using-requestidlecallback#guaranteeing_your_function_is_called -
V8 Code Cache -
https://v8.dev/blog/code-caching
-
New JavaScript techniques for rapid page loads -
https://blog.chromium.org/2015/03/new-javascript-techniques-for-rapid.html
以上是关于VS Code 是如何优化启动性能的?的主要内容,如果未能解决你的问题,请参考以下文章