前言
介绍了为什么 Chrome DevTools 在处理生产环境中的性能问题时不足以完全满足需求,以及如何通过生产环境中的性能分析来改进 Web 应用程序的性能。今日前端早读课文章由 @Amila Welihinda 分享,@飘飘编译。
随着 Web 应用越来越复杂,再加上 JavaScript 是单线程的,Web 应用的扩展性问题变得更加棘手。Chrome DevTools 是我们维持和优化 Web 应用性能的重要工具,但在本地进行性能分析,往往无法真实反映终端用户实际遇到的性能问题。为了更快、更有效地发现并修复性能问题,包括 Facebook、Microsoft、Slack、Dropbox 和 Notion 在内的一些公司已经开始在生产环境中进行性能分析。本文将介绍他们在生产环境中进行性能分析的一些通用方法。
Web 应用难以扩展的一个根本原因,是 JavaScript 的单线程特性。浏览器中的所有工作都必须在 16 毫秒内完成,才能使页面体验达到每秒 60 帧,这意味着你的应用代码要和垃圾回收、浏览器的渲染流程(渲染 / 布局、绘制、合成)争夺这 16 毫秒的时间。一旦超时,浏览器就会掉帧,用户会感受到界面卡顿。
长任务阻塞用户输入的示意图
现在的大型 Web 应用(比如 VSCode、Slack、Notion、Discord、Spotify 等)通常拥有数百万行 JavaScript 代码,这让扩展它们变得更加困难。尽管这些公司都有专门的性能团队,但依然面临维护和优化性能的巨大挑战。而且,单线程性能已经逐渐触顶,难以继续提升。
单线程 JavaScript 核心性能瓶颈图示
随着 Web 应用的规模持续增长,保持其性能变得越来越难,用户也开始明显感觉到应用变慢了。
用户抱怨 Web 应用变慢的截图
Web 开发人员面对性能问题时,能用的工具主要就是 Chrome DevTools,但它在解决终端用户性能问题上确实存在一些局限。
虽然 Chrome DevTools 是性能工程师最常用的工具,但它也有几个关键问题:
1、难以复现终端用户的性能问题。
终端用户的硬件差异、应用状态不同、操作路径各异,都会导致 Chrome DevTools 无法准确反映实际遇到的性能瓶颈。要准确复现这些问题,通常需要用户提供详细的操作步骤和重现方式,然后开发者还要搭建出相同的运行环境和操作流程。但这种依赖用户主动汇报的方式不稳定,而且大多数性能问题根本不会被用户报告出来。
2、性能回退难以定位。
每次发布新版本时,都可能引入性能回退。在大型项目中,一个版本可能合并了上百个来自不同团队的提交,这使得回退定位变得极其困难。我们见过有些团队会靠猜测回退某些改动来试图恢复性能,但通常没有效果。如果一次回退无效,他们就继续回退其他改动,直到性能指标恢复正常,或者干脆把新指标作为 “新基线” 来接受。
3、优化点难以发现。
由于缺乏对实际慢路径的可见性,开发者很难发现真正值得优化的地方。
Chrome DevTools 无法真实代表终端用户的代码执行路径,这是根本性的限制。为了克服这些问题,一些公司开始探索在生产环境中进行性能分析的新方法。
因为在生产环境中看不到 JavaScript 的真实执行情况,Facebook 的一个团队在 2021 年 9 月决定尝试解决这个问题,开展了一次在生产环境中分析 JavaScript 执行性能的实验。
他们从这次实验中得到了如下发现:
这个 JavaScript 性能分析器只在少量 Facebook 用户中启用,并且只分析包含 10 条语句以上的函数,以减少对性能的影响并控制分析数据的量。尽管如此,我们依然认为它在了解 Facebook.com 实际性能表现以及发现优化机会方面价值极高。
—— Andrew Comminos(Facebook 高级工程师)
Facebook 的这项早期实验,为 Chrome 团队标准化 “自我性能分析 API(Self-Profiling API)” 奠定了基础。
Self-Profiling API 是一种可以以编程方式高效分析终端用户 JavaScript 性能的接口。Slack、Microsoft、Facebook、Dropbox、Notion 等公司都使用它来分析终端用户浏览器中的 JavaScript 执行情况。
启动该性能分析器需要两个配置参数:
sampleInterval - 采样间隔时间(单位:毫秒),用于控制分析器对 CPU 的影响;
maxBufferSize - 采样缓冲区最大容量(单位:样本数),用于控制分析器的内存占用。
以下是一个使用 Self-Profiling API 对 React 渲染进行分析的示例:
// React 渲染性能分析示例
const profiler = new Profiler({sampleInterval:10,maxBufferSize:10_000});
react.render(<App />);
const trace = await profiler.stop();
sendProfile(trace);
Facebook 在生产环境中启用 Self-Profiling API 后,发现页面加载性能影响小于 1%(p=0.05)。在 Palette,我们的一些大客户(用户数超过 1 亿)启用该功能后,也没有报告性能下降的问题。
鉴于 Chrome DevTools 无法深入了解终端用户实际运行时的慢代码路径,我们提出一个问题:“如果 Chrome DevTools 的性能分析器可以使用终端用户的真实数据会怎样?”
这带来了很多实用场景,比如针对具体用户:
识别第三方脚本对页面加载的影响;
找出 React 中慢的 hooks 和渲染逻辑;
分析应用代码中阻塞用户交互的部分。
在大规模应用中,单独查看每个用户的性能数据是不现实的。因此,将所有采集到的分析数据合并成一个统一的概要信息是更可行的做法。
以下是合并后的性能分析视图示例:
用户抱怨 Web 应用变慢的截图
单个用户的分析数据可以帮助我们理解该用户的具体问题,而合并后的数据则能帮助我们从整体角度了解所有用户的性能瓶颈。
例如,通过合并数据可以回答以下问题:
哪些第三方脚本在所有用户中最影响页面初始渲染?
哪些 React hooks 和渲染逻辑影响了用户交互性能?
合并的性能数据还可以让我们轻松对比回退前后的函数执行时间。比如,你开发了一个文本编辑器,突然发现输入变慢了。这时你可以对比发生性能问题前后的版本,找出哪些函数的执行时间变长了。
下面是一个对比分析的示意图,绿色表示函数执行时间减少,红色表示增加:
用户抱怨 Web 应用变慢的截图
只要采样数据足够多(Palette 推荐收集 100 万条堆栈样本),合并后的数据就足够稳定,能帮助你准确识别回退问题。
以下是一些通过比较合并数据能回答的问题:
哪些第三方脚本导致页面加载性能在所有用户中下降?
哪些 React hooks 和渲染逻辑在所有用户中影响了交互性能?
在 Palette,我们构建了性能分析聚合(Profile Aggregation)和聚合对比(Profile Aggregate Comparison)功能。
下面是 Palette 中一个合并后的火焰图示例:
用户抱怨 Web 应用变慢的截图
这是一个合并后对比火焰图的示例:
用户抱怨 Web 应用变慢的截图
这些功能的核心就是前面提到的合并与对比概念,在此基础上还添加了强大的筛选功能。
通过筛选功能,你可以聚焦某个具体指标、页面或版本中运行的函数,回答更复杂的性能问题,比如:“我们应用主页最新版本中,是哪些函数导致了自定义指标 typing_lag
(输入延迟)恶化?”
以下是合并对比火焰图的一个示例:
用户抱怨 Web 应用变慢的截图
Palette 支持以下筛选条件:
以下是筛选功能可以帮助回答的一些问题:
哪些函数在低端设备上导致性能下降?(例如:筛选条件 cores < 4
)
哪些函数导致某个用户的输入性能变差?(例如:tag 用户 ID = 'u-123'
)
哪些函数在我们应用首页的新版本中导致页面加载变慢?(例如:path = '/'
且 version = 'v1.2.3'
)
Palette 支持上传 Source Map,可以展示未压缩的函数名称和源代码,还进一步提供了函数性能回退的代码差异对比(Code Diff)功能。下面是一个示例,展示了导致 Notion 输入性能下降的代码差异:
用户抱怨 Web 应用变慢的截图
在构建完 Profile Aggregation(性能聚合)和 Comparison(性能对比)功能后,Notion 是 Palette 第一批在生产环境中大规模使用这些功能的客户之一。
通过使用 Palette,Notion 成功提升了输入响应速度、表格交互性能,并修复了加载和输入相关的性能回退问题。具体成果包括:
页面加载延迟降低了 15–20%;
输入延迟降低了 15%;
成功识别了 60% 的性能回退原因;
有些性能问题的解决时间缩短了 3–4 周。
Notion 使用一个名为 initial_page_render (IPR)
的内部自定义指标来衡量文档加载性能。这项指标对新增的初始化代码非常敏感,因此经常出现回退问题。
在集成 Palette 后,Notion 团队一旦发现性能回退,就使用 Profile Aggregate Comparison 分析是哪段代码导致了 IPR 的下降。他们在合并后的火焰图中发现了如下异常:
用户抱怨 Web 应用变慢的截图
即使 Notion 的代码库已超过 100 万行,他们依然能清晰地定位是哪段新代码影响了文档渲染。Notion 的一位工程师这样说道:
“通过 Profile Aggregate Comparison,我发现有人把本应延迟加载的代码放进了主 bundle 中,并在页面加载时就执行了。这段代码在火焰图上特别显眼。”
—— Carlo Francisco(Notion 性能工程师)
想进一步了解他们如何使用 Palette 解决更多性能问题,请阅读完整案例研究:https://palette.dev/blog/improving-notion-typing-performance
使用 Self-Profiling API 在生产环境中进行性能分析,是 Web 性能优化的下一片蓝海。它让我们能够发现 Chrome DevTools 无法捕捉到的终端用户性能问题。
我们衡量性能的标准应该是 “对终端用户来说哪里慢”,而不是 “在本地测试哪里慢”。生产环境的性能分析,正是实现这一目标的关键工具。
还没有使用过我们刷题网站(https://fe.ecool.fun/)或者前端面试题宝典的同学,如果近期准备或者正在找工作,千万不要错过,题库主打无广告和更新快哦~。
有会员购买、辅导咨询的小伙伴,可以通过下面的二维码,联系我们的小助手。