系统性能衡量及优化
time: 2020.6.9
目录
背景
问题
- 我们为什么要做性能优化?性能优化在项目中的重要性如何?-任职要求调试与性能优化;提升用户体验、提升开发体验
- 什么样的性能算好性能?-指标
- 性能如何衡量?-计算方式,api,工具
- 如何做性能优化?
- 性能优化对开发人员有什么要求?
在如今浏览器端渲染占主流地位时,对前端性能要求也越来越高,比如一个 spa 应用,用户首次进入到可操作 UI,通常耗时在3s左右;
如果再搭配微前端,子应用又是一个独立的应用,时间往往又会长一些。此刻,前端性能优化能力已是衡量前端工程师的一个关键指标,能解决企业网页性能问题,是企业更需要的人才。
当然,性能优化,是需要一个掌握全面前端开发技术的工程师来处理,精确把控各问题点及解决方案。
1 系统性能衡量要点
待完善,输出性能指标文档,统一公司规范
- 加载:加载要求在 1s 内看到页面
- 响应:点击按钮在 100 ms 内响应,滚动无卡顿
- 动画:动画 60 fps
- 内存占用不能过高
- main task < 50ms
- lcp < 2.5s
- 构建性能 - 加强开发体验
2 影响性能因素
2.1 加载时
能阻塞浏览器渲染的,目前只有 html, css, js 文件,而图片、音频、视频不会阻塞。可以结合渲染流程来看。那么图片等其他资源怎么处理呢?异步
- 资源个数:每个资源一个 http 请求,浏览器对同时发起请求数量有限制
- 资源大小:tcp 分包发送,每一个包的发送都需要时间,也就是 rtt。解析时,如果资源文件过大,解析也会耗时过长
- 网络:如果服务器带宽、客户端网络不好,则会加大 rtt 时间
- 浏览器插件启用数:插件会占用主线程执行
2.2 运行时
运行时的性能问题,通常是用户操作反馈不流畅。通常原因是浏览器渲染更新频率过高、主线程执行时间过长。我们尽量减少一帧的生成时间
重排:由 dom 结构的更改、几何位置变更触发重排,触发一次 rerender,会走 update dom tree
、样式 recalculate
、layout
、update layer tree
、paint
重绘:改变元素颜色一类信息,只需要 paint
,直接依据原有 layer tree + 新的样式 来重新绘制,不需要 update dom tree、样式 recalculate、layout、update layer tree
有如下影响渲染的因素
- js 任务执行过长:处理大量数据,长时间处于循环中
- 及时获取异步渲染结果:同步代码中,包含了异步渲染(更改几何高宽、颜色),如果及时获取节点属性,则会强行在当前任务中执行渲染过程,以便同步获取渲染结果
- js 生成动画:js 改变 dom 来实现动画,现在基本不会这么做了。css 动画可以只由合成线程来处理,脱离主线程
- 创建过多细小对象:gc 任务阻断主线程
注意:连续设置 dom.style.height 不会有多个 rerender 任务,这个是多年以来的误解
2.3 开发时
webpack 构建时长、热更新时长
2.4 系统稳定性优化
包括可维护性,系统鲁棒性优化
3.1 浏览器任务管理器
- 浏览器所有进程
- 进程占用的存储空间、cpu、网络、进程 id
nodejs 使用 pm2 monit
命令查看
在 chrome-dev-tool 教程 中,值得看的是 performance 和 memory
内存原理在这里看,这里总结如何使用工具

FPS: frame per second
HEAP: 表示 js heap 数据积累量
Main:主要看 main 内事件任务,红色的代表有问题,需要查看调用栈,看看具体任务代码的执行时间
Timings 有几个指标
- DCL: DomContentLoaded event, 1704.7ms
- FP: first paint, 1751.8ms
- FCP: first contentful paint, 页面开始绘制第一个像素点, 1751.8ms
- L: Onload event, 1757.0 ms
- LCP: largest contentful paint, 页面最大图像或文本块像素点绘制完毕, 2074.9ms
- FMP: first meaningful paint, 3162.3ms (对于哪些数据是 meaningful 的,这个不好定义)
- FID: first input delay,首次用户交互延迟
- CLS: cumulative layout shift, 积累的待渲染布局,表示可视化ui稳定性
通常我们计算白屏时间,指的就是 FP 之前的时间,但是 FP 没法通过浏览器 api 获取,所以通常是用 DCL 时间来算;而首页渲染完毕时间,指的是 LCP 时间,而渲染过程时间通常是 300 ms
统计 api: window.performance.timing
- 白屏时间 = domloading - fetchStart,即在发起请求,到浏览器开始解析 html 的时间。为什么不是 domInteractive 或 domComplete,因为在解析 html 过程中就会渲染
- 首屏时间 = domComplete - domloading,也称为解析 html 时间 + css、js 加载解析完毕时间
- 初次请求耗时(通常是 html) = responseEnd - requestStart
- 其他资源请求耗时 = loadEventStart - domContentLoadedEventEnd (这个也不太准,js会阻塞 dom 解析)
- 所有资源请求耗时 = loadEventStart - fetchStart,从浏览器开始发起请求,到所有资源加载完毕
- dom 解析时长 = domComplete - responseEnd
web-vitals: LCP, FID, CLS
3.3 lighthouse
加载时性能评分、性能优化建议
Speed_index:页面被明显填满的平均时间
Time to Interactive:开始加载到可以响应用户操作的时间
3.4 memory 及内存泄漏分析
js heap 分析,实时内存分析、统计
内存泄漏:随着系统使用时间增加,如果存在内存泄漏,那么系统内存占用会越来越多,性能就会越来越卡
内存膨胀:系统开始就很卡,因为内存占用就超过了最佳性能的阈值
卡的原因:内存占用过多,gc任务就会频繁,每次 gc 都会暂停 js 执行,所以就卡了
3.4.1 检测方式 - JS HEAP
通过操作界面关键步骤,比如切换 tab、菜单来检测
- 使用 performance 查看 HEAP 线条变化,如果没有下降或者下降很少,那么可能存在内存泄漏
- 使用 memory 模块,不停切换菜单,如果看到 HEAP 不停增加,那么就存在内存泄漏
3.4.2 检测方式 - Detached dom node
通过 Take Heap snapshot 来收集内存使用情况,在过滤处输入 Detached
来查看已经不存在于 dom tree 中但是还被 js 变量引用的 dom 节点对象。
因为此类 dom 节点,可能已经不再需要了,没有及时得到释放;也有可能是做的优化缓存,后续直接添加到 dom tree 上。
猜测不可能是没有来得及垃圾回收的节点,因为每次 snapshot 之前会有一次强行的 gc 任务执行
3.4.3 检测方式 - 查看新分配的内存
- Allocation instrumentation on timeline:按时间线记录内存分配详情
- Aoolcation sampling:定位到具体分配内存函数,支持 chart、heavy 查看调用栈信息
这里查看不到被回收的内存
3.4.4 Memory Inspector
用于调试查看 ArrayBuffer, wasm 模块的 memory 数据
实时检测内存、cpu等使用情况
3.6 rendering
4 性能优化
4.1 构建优化
webpack
- dll, extenals, splitChunks.cacheGroups
- hardSourcePlugin
- 减小解析范围:使用 resolve.modules;减少 resolve.modules, extensions, mainFiles 中的条目;设置 resolve.symlinks: false
- loader 通过 include 指定目录控制其范围
- 使用
thread-loader
启用多个 worker,用于独立处理耗时长的 loader
- cache 配置,适用于 lib?
- devtool 使用
cheap-source-map
来提升性能
- 设置 output.pathinfo: false 来取消打包结果资源 bundle 的路径信息,减少垃圾回收压力
- 使用
parallel-webpack
+ cache-loader
优化具有多个 compilation 场景。啥时候会有多个?生产不都是一个?
- externals:减少打包文件体积
其他
- 可以构建 bundleless 产物,减少构建时长
4.1 加载优化
- http 缓存
cache-control:max-age=2000
,缓存 2000s
- 资源合并、内联
- 资源压缩 + 服务器配置 gzip 压缩
- cdn
- 按需加载(懒加载):首次加载首页需要的资源文件就行,懒加载
- 升级 http2,采用多路复用,解决对资源个数的限制,也能解决请求过多的问题
- 数据缓存:使用浏览器提供的前端存储机制,将静态资源、接口数据存储在 cache, disk。webpack 修改文件 hash 名,
moduleIds: 'deterministic'
- 预获取:使用
<link rel="prefetch" href="xxx.js">
预获取文件,可以使用 webpackPrefetch 实现模块预获取。表示浏览器在空闲时间去获取资源
- 预加载:使用
<link rel="preload" href="xxx.js">
预加载文件,可以使用 webpackPreload 实现模块预加载。表示浏览器在请求当前资源时,也会并行加载 preload 的资源
- 禁用浏览器插件:浏览器插件会对资源文件、dom节点做拦截和其他处理,同样会占用 cpu 资源,导致主线程阻塞
- dns 预解析:
dns-prefetch
添加在跨域资源上,实现跨域资源 IP 预获取
2021-12-02 20:34:38 更新
问题:甩过来一个网站,直接让你做加载时的性能优化,解决用户初始等待过长和切换卡死问题
问题分析:加载时间很长或页面卡死,这里就有可能是请求数量过多、资源体积过大、js 任务过长、js 报错等问题
分析工具:使用 lighthouse 做初步分析,根据其优化方案来做针对性的性能优化,比如可以快速配置 http2 + gzip 压缩来解决初步问题,后续再做代码层面的优化
4.2 运行时优化
- 减少 js 任务执行时间:大任务分片、worker 辅助
- 避免强制同步:保证异步渲染任务独立 (尽量减少 console.log 等方式获取节点渲染结果属性)
- 采用 css 生成动画:css 动画由合成线程来处理,不阻塞主线程;可以让节点成为独立的层,比如增加 will-change、fixed、relative 等属性
- 降低内存占用:减少细小对象的创建、内存泄漏处理、函数式组件、list 分片渲染、普通内存转 locastorage 或 indexedDB 等
- 浏览器空闲之行任务:使用 requestIdleCallback 来跑低优先级任务,避免阻塞关键任务
- 使用 requestAnimationFrame 来替代 setTimeout 跑分片任务,但是在后台标签页不会执行 requestAnimationFrame 函数
5 实践落地
2021-11-24 10:52:11
前端工程化:
- 方向:前端构建、代码开发、页面加载及运行、监控及告警,完整流程上做流程优化,做到数据可衡量
- 复杂度:里面包含了 webpack 源码、ast、浏览器原理、后端开发、高并发、各种解决方案等复杂技术及流程
- 个人提升:完整软件开发者一套技术体系,是值得长期学习实践的技术,是业界最通用的技术。个人 T 型人才发展,后续对可视化、webXR、webassembly 等技术的学习也会触类旁通
系统性能衡量及优化,是前端工程化中核心一环。目前自己虽然有一定的知识积累,但是还是缺乏实践
- 构建阶段:会 webpack 优化配置,了解一定构建原理,存在如下问题:缺乏 loader和plugin的编写经验(ast)、缺乏 es module 构建工具使用、缺乏构建性能衡量实践
- 开发阶段:对 es module 的使用不是那么熟悉,后续新项目都尽量使用 vue3 来开发。vue-cli 支持 webpack5 到现在还是在 rc 阶段
- 性能优化:对内存泄漏问题分析、Performance.timing 缺少实践
- 工程化 - 前端监控系统:错误监控、请求错误监控、错误上报缺少实践
- 网络:tls, ssl,非对称加密流程不熟悉,抓包工具 wireshark 不熟悉
参考文章
chrome performace
Speed_index
前端性能优化清单
以用户为中心的性能指标
web vitals
chrome 90 最新知识点
webpack 构建性能优化