Blog

系统性能衡量及优化

time: 2020.6.9

目录

背景

问题

  1. 我们为什么要做性能优化?性能优化在项目中的重要性如何?-任职要求调试与性能优化;提升用户体验、提升开发体验
  2. 什么样的性能算好性能?-指标
  3. 性能如何衡量?-计算方式,api,工具
  4. 如何做性能优化?
  5. 性能优化对开发人员有什么要求?

在如今浏览器端渲染占主流地位时,对前端性能要求也越来越高,比如一个 spa 应用,用户首次进入到可操作 UI,通常耗时在3s左右; 如果再搭配微前端,子应用又是一个独立的应用,时间往往又会长一些。此刻,前端性能优化能力已是衡量前端工程师的一个关键指标,能解决企业网页性能问题,是企业更需要的人才。

当然,性能优化,是需要一个掌握全面前端开发技术的工程师来处理,精确把控各问题点及解决方案。

1 系统性能衡量要点

待完善,输出性能指标文档,统一公司规范

  1. 加载:加载要求在 1s 内看到页面
  2. 响应:点击按钮在 100 ms 内响应,滚动无卡顿
  3. 动画:动画 60 fps
  4. 内存占用不能过高
  5. main task < 50ms
  6. lcp < 2.5s
  7. 构建性能 - 加强开发体验

2 影响性能因素

2.1 加载时

能阻塞浏览器渲染的,目前只有 html, css, js 文件,而图片、音频、视频不会阻塞。可以结合渲染流程来看。那么图片等其他资源怎么处理呢?异步

  1. 资源个数:每个资源一个 http 请求,浏览器对同时发起请求数量有限制
  2. 资源大小:tcp 分包发送,每一个包的发送都需要时间,也就是 rtt。解析时,如果资源文件过大,解析也会耗时过长
  3. 网络:如果服务器带宽、客户端网络不好,则会加大 rtt 时间
  4. 浏览器插件启用数:插件会占用主线程执行

2.2 运行时

运行时的性能问题,通常是用户操作反馈不流畅。通常原因是浏览器渲染更新频率过高、主线程执行时间过长。我们尽量减少一帧的生成时间
重排:由 dom 结构的更改、几何位置变更触发重排,触发一次 rerender,会走 update dom tree、样式 recalculatelayoutupdate layer treepaint
重绘:改变元素颜色一类信息,只需要 paint,直接依据原有 layer tree + 新的样式 来重新绘制,不需要 update dom tree、样式 recalculate、layout、update layer tree

有如下影响渲染的因素

  1. js 任务执行过长:处理大量数据,长时间处于循环中
  2. 及时获取异步渲染结果:同步代码中,包含了异步渲染(更改几何高宽、颜色),如果及时获取节点属性,则会强行在当前任务中执行渲染过程,以便同步获取渲染结果
  3. js 生成动画:js 改变 dom 来实现动画,现在基本不会这么做了。css 动画可以只由合成线程来处理,脱离主线程
  4. 创建过多细小对象:gc 任务阻断主线程

注意:连续设置 dom.style.height 不会有多个 rerender 任务,这个是多年以来的误解

2.3 开发时

webpack 构建时长、热更新时长

2.4 系统稳定性优化

包括可维护性,系统鲁棒性优化

3 性能分析工具 chrome-dev-tool

3.1 浏览器任务管理器

  1. 浏览器所有进程
  2. 进程占用的存储空间、cpu、网络、进程 id

nodejs 使用 pm2 monit 命令查看

3.2 performance

chrome-dev-tool 教程 中,值得看的是 performance 和 memory

内存原理在这里看,这里总结如何使用工具

performance

FPS: frame per second
HEAP: 表示 js heap 数据积累量
Main:主要看 main 内事件任务,红色的代表有问题,需要查看调用栈,看看具体任务代码的执行时间

Timings 有几个指标

  1. DCL: DomContentLoaded event, 1704.7ms
  2. FP: first paint, 1751.8ms
  3. FCP: first contentful paint, 页面开始绘制第一个像素点, 1751.8ms
  4. L: Onload event, 1757.0 ms
  5. LCP: largest contentful paint, 页面最大图像或文本块像素点绘制完毕, 2074.9ms
  6. FMP: first meaningful paint, 3162.3ms (对于哪些数据是 meaningful 的,这个不好定义)
  7. FID: first input delay,首次用户交互延迟
  8. CLS: cumulative layout shift, 积累的待渲染布局,表示可视化ui稳定性

通常我们计算白屏时间,指的就是 FP 之前的时间,但是 FP 没法通过浏览器 api 获取,所以通常是用 DCL 时间来算;而首页渲染完毕时间,指的是 LCP 时间,而渲染过程时间通常是 300 ms

统计 api: window.performance.timing

  1. 白屏时间 = domloading - fetchStart,即在发起请求,到浏览器开始解析 html 的时间。为什么不是 domInteractive 或 domComplete,因为在解析 html 过程中就会渲染
  2. 首屏时间 = domComplete - domloading,也称为解析 html 时间 + css、js 加载解析完毕时间
  3. 初次请求耗时(通常是 html) = responseEnd - requestStart
  4. 其他资源请求耗时 = loadEventStart - domContentLoadedEventEnd (这个也不太准,js会阻塞 dom 解析)
  5. 所有资源请求耗时 = loadEventStart - fetchStart,从浏览器开始发起请求,到所有资源加载完毕
  6. 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、菜单来检测

  1. 使用 performance 查看 HEAP 线条变化,如果没有下降或者下降很少,那么可能存在内存泄漏
  2. 使用 memory 模块,不停切换菜单,如果看到 HEAP 不停增加,那么就存在内存泄漏

3.4.2 检测方式 - Detached dom node

通过 Take Heap snapshot 来收集内存使用情况,在过滤处输入 Detached 来查看已经不存在于 dom tree 中但是还被 js 变量引用的 dom 节点对象。
因为此类 dom 节点,可能已经不再需要了,没有及时得到释放;也有可能是做的优化缓存,后续直接添加到 dom tree 上。

猜测不可能是没有来得及垃圾回收的节点,因为每次 snapshot 之前会有一次强行的 gc 任务执行

3.4.3 检测方式 - 查看新分配的内存

  1. Allocation instrumentation on timeline:按时间线记录内存分配详情
  2. Aoolcation sampling:定位到具体分配内存函数,支持 chart、heavy 查看调用栈信息

这里查看不到被回收的内存

3.4.4 Memory Inspector

用于调试查看 ArrayBuffer, wasm 模块的 memory 数据

3.5 performance monitor

实时检测内存、cpu等使用情况

3.6 rendering

4 性能优化

4.1 构建优化

webpack

  1. dll, extenals, splitChunks.cacheGroups
  2. hardSourcePlugin
  3. 减小解析范围:使用 resolve.modules;减少 resolve.modules, extensions, mainFiles 中的条目;设置 resolve.symlinks: false
  4. loader 通过 include 指定目录控制其范围
  5. 使用 thread-loader 启用多个 worker,用于独立处理耗时长的 loader
  6. cache 配置,适用于 lib?
  7. devtool 使用 cheap-source-map 来提升性能
  8. 设置 output.pathinfo: false 来取消打包结果资源 bundle 的路径信息,减少垃圾回收压力
  9. 使用 parallel-webpack + cache-loader 优化具有多个 compilation 场景。啥时候会有多个?生产不都是一个?
  10. externals:减少打包文件体积

其他

  1. 可以构建 bundleless 产物,减少构建时长

4.1 加载优化

  1. http 缓存 cache-control:max-age=2000,缓存 2000s
  2. 资源合并、内联
  3. 资源压缩 + 服务器配置 gzip 压缩
  4. cdn
  5. 按需加载(懒加载):首次加载首页需要的资源文件就行,懒加载
  6. 升级 http2,采用多路复用,解决对资源个数的限制,也能解决请求过多的问题
  7. 数据缓存:使用浏览器提供的前端存储机制,将静态资源、接口数据存储在 cache, disk。webpack 修改文件 hash 名,moduleIds: 'deterministic'
  8. 预获取:使用 <link rel="prefetch" href="xxx.js"> 预获取文件,可以使用 webpackPrefetch 实现模块预获取。表示浏览器在空闲时间去获取资源
  9. 预加载:使用 <link rel="preload" href="xxx.js"> 预加载文件,可以使用 webpackPreload 实现模块预加载。表示浏览器在请求当前资源时,也会并行加载 preload 的资源
  10. 禁用浏览器插件:浏览器插件会对资源文件、dom节点做拦截和其他处理,同样会占用 cpu 资源,导致主线程阻塞
  11. dns 预解析:dns-prefetch 添加在跨域资源上,实现跨域资源 IP 预获取

2021-12-02 20:34:38 更新
问题:甩过来一个网站,直接让你做加载时的性能优化,解决用户初始等待过长和切换卡死问题
问题分析:加载时间很长或页面卡死,这里就有可能是请求数量过多、资源体积过大、js 任务过长、js 报错等问题
分析工具:使用 lighthouse 做初步分析,根据其优化方案来做针对性的性能优化,比如可以快速配置 http2 + gzip 压缩来解决初步问题,后续再做代码层面的优化

4.2 运行时优化

  1. 减少 js 任务执行时间:大任务分片、worker 辅助
  2. 避免强制同步:保证异步渲染任务独立 (尽量减少 console.log 等方式获取节点渲染结果属性)
  3. 采用 css 生成动画:css 动画由合成线程来处理,不阻塞主线程;可以让节点成为独立的层,比如增加 will-change、fixed、relative 等属性
  4. 降低内存占用:减少细小对象的创建、内存泄漏处理、函数式组件、list 分片渲染、普通内存转 locastorage 或 indexedDB 等
  5. 浏览器空闲之行任务:使用 requestIdleCallback 来跑低优先级任务,避免阻塞关键任务
  6. 使用 requestAnimationFrame 来替代 setTimeout 跑分片任务,但是在后台标签页不会执行 requestAnimationFrame 函数

5 实践落地

2021-11-24 10:52:11

前端工程化:

  1. 方向:前端构建、代码开发、页面加载及运行、监控及告警,完整流程上做流程优化,做到数据可衡量
  2. 复杂度:里面包含了 webpack 源码、ast、浏览器原理、后端开发、高并发、各种解决方案等复杂技术及流程
  3. 个人提升:完整软件开发者一套技术体系,是值得长期学习实践的技术,是业界最通用的技术。个人 T 型人才发展,后续对可视化、webXR、webassembly 等技术的学习也会触类旁通

系统性能衡量及优化,是前端工程化中核心一环。目前自己虽然有一定的知识积累,但是还是缺乏实践

  1. 构建阶段:会 webpack 优化配置,了解一定构建原理,存在如下问题:缺乏 loader和plugin的编写经验(ast)、缺乏 es module 构建工具使用、缺乏构建性能衡量实践
  2. 开发阶段:对 es module 的使用不是那么熟悉,后续新项目都尽量使用 vue3 来开发。vue-cli 支持 webpack5 到现在还是在 rc 阶段
  3. 性能优化:对内存泄漏问题分析、Performance.timing 缺少实践
  4. 工程化 - 前端监控系统:错误监控、请求错误监控、错误上报缺少实践
  5. 网络:tls, ssl,非对称加密流程不熟悉,抓包工具 wireshark 不熟悉

参考文章

chrome performace
Speed_index
前端性能优化清单
以用户为中心的性能指标
web vitals
chrome 90 最新知识点
webpack 构建性能优化