此处截图省略了一些内部信息,一般情况下,如果需要更多信息可以借助Source模块来找到引起主线程密集计算的代码位置 。在这个例子中,这个调用未触发 longtask,并且我们很容易发现这就是 SDK 初始化的逻辑,也是接下来需要优化的地方 。
问题分析与性能优化通过上述 benchmark 工具和 perfsee lab 性能分析结果,我们可以看出 SDK 初始化逻辑以及大量的事件监听确实对业务性能造成了一定影响 。例如上文火焰图中所示每一个onBFCacheRestore
都占用了超过 15ms 的时间,我们在源码里搜索这个函数,此部分伪代码如下:
const onBFCacheRestore = (cb) => {addEventListener('pageshow', (e) => {if (e.persisted) cb(e)}, true)}
BFCache 即 back-forward cache,可称为“往返缓存”,可以在用户使用浏览器的“后退”和“前进”按钮时加快页面的转换速度 。这个缓存不仅保存页面数据,还保存了 DOM 和 JS 的状态,实际上是将整个页面都保存在内存里 。如果页面位于 BFCache 中,那么再次打开该页面就不会触发 onload 事件 。可以看到,耗时主要由 onBFCacheRestore 和 onHidden 两个方法中的原生 addEventListener 造成 。这些监听本身都是在毫秒级的,回调函数也没有什么优化空间,从实际场景考虑,这两处回调是为了监听用户页面前进和返回的,并非优先级最高的任务 。我们可以从以下几个方面降低对业务造成的影响:
1. 监控任务切片运行,区分优先级对于监控 SDK 而言,除了必要的监听以及事件预收集等任务,其他任何任务不应该阻碍到业务代码的执行 。对于字节前端监控需求而言,异常和请求监听为必须前置执行的任务,其他所有事件监听可以拆分为单独的任务,所有的采样、数据运算、上报请求等数据后处理逻辑只在空闲时执行,通过 requestIdleCallback 调用 。
2. 减少重复监听次数多个性能指标监听同一事件的公用监听器,例如 CLS 和 LCP 这两个指标都需要监听 onBFCacheRestore,让他们只做一次 addEventListener 。
3. 请求数量的优化我们 SDK 的脚本是由一个必须最先执行的主脚本(包含预收集、请求hook、错误监听等逻辑)和多个通过不同配置开启的异步插件脚本(性能、资源、白屏等)组成,主脚本的请求无法省略,而插件脚本可以通过接入 cdn combo 服务或自行搭建 combo 服务将多个请求合并成一个 。对于事件上报请求,我们在内部维护一个缓存,只有当间隔达到一定时间或者累计一定数量之后才会统一上报 。在这个场景中,我们又需要考虑两个问题:
- 浏览器对请求并发量有限制,所以存在网络资源竞争的可能性
- 浏览器在页面卸载时会忽略异步ajax请求,而同步 ajax 通常在现代浏览器中已被禁用
这个方法主要用于满足统计和诊断代码的需要,这些代码通常尝试在卸载(unload)文档之前向 Web 服务器发送数据 。过早的发送数据可能导致错过收集数据的机会 。然而,对于开发者来说保证在文档卸载期间发送数据一直是一个困难 。因为用户代理通常会忽略在经过以上优化后,我们注入优化过后的 SDK 再次跑分 。unload (en-US)
事件处理器中产生的异步XMLHttpRequest

文章插图
优化后的 SDK 对业务 FCP、LCP、LOAD 等性能的影响已经降到了最低,已经达到了非常高的性能标准 。
了解更多字节内部众多业务方使用的前端监控解决方案已同步在火山引擎上,无论是外部企业开发者或个人开发者,均可通过接入该服务提升性能优化的效率 。
经验总结扩展阅读
- 四 Selenium4.0+Python3系列 - 常见元素操作(含鼠标键盘事件)
- iPhone14Pro满电无法开机是怎么回事 iPhone14系列糟点有哪些
- iQOO8系列配置_iQOO8系列参数详情
- 30 《吐血整理》高级系列教程-吃透Fiddler抓包教程-Fiddler如何抓取Android7.0以上的Https包-番外篇
- 从0搭建vue3组件库: 如何完整搭建一个前端脚手架?
- 树的邻接矩阵、双亲孩子表示法…… C++ 不知树系列之初识树
- .NET Core C#系列之XiaoFeng.Data.IQueryableX ORM框架
- flutter系列之:永远不用担心组件溢出的Wrap
- 荣耀x20评测_荣耀x20评测表现
- 抛砖系列之redis监控命令