狙杀页面卡顿 —— Performance 指北

本文首发于本肥宅的博客及知乎饿了么前端专栏css

今天介绍下 Chrome dev tools 家族的一个小兄弟,它在 Chrome 57 以前叫做「Timeline」,而如今换了个更长的马甲 —— 「Performance」,毕竟名字要「长~~~~~~~~~」更能吸引注意。html

也许你曾不经意启动过这个工具,看见里面五光十色的图表后和我同样头晕目眩。但今天介绍完它后,我相信你能像熟悉瑞士军刀同样熟悉它。前端

这个面板叫作「Performance」,不过名字里也没有指明是什么性能。既然是 Chrome 的调试工具,那应该和页面有关系,咱们就从页面性能聊起。git

什么会影响你的页面性能

近年来,WEB 开发者们为缩短用户等待时间作出了一系列方案,以期「短益求短」。好比用 PWA 缓存更多可用的离线资源,让网页应用打开更快;借助 WebAssembly 规范缩小资源体积,提升执行效率。这些方案分别着眼于网络链路,前端资源处理速度等维度上,致力提升用户体验。github

做为 WEB 开发者,我感觉到跟页面性能挂钩比较深的几个维度是:网络链路、服务器资源、前端资源渲染效率、用户端硬件。web

网络链路

网络链路每每是页面性能的扼要之处,域名解析、交换机、路由器、网络服务提供商、内容分发网络、服务器,链路上的节点出问题或响应过慢都会有很差的体验。算法

生活

服务器资源

在 HTTP 的大环境下,全部请求最终都要服务器来处理,服务器爸爸处理不当没法响应或响应过慢也会直接影响页面与用户的互动。chrome

前端资源渲染

浏览器获取所需 HTML、CSS、脚本、图片等静态资源,绘制首屏呈现给用户的过程;或用户与页面交互后,浏览器从新计算须要呈现的内容,而后从新绘制的过程。这些过程的处理效率也是影响性能的重要因素。npm

用户硬件

发起网络请求,解析网络响应,页面渲染绘制等过程都须要消耗计算机硬件资源。因此计算机资源,特别是 CPU 和 GPU 资源短缺时(好比打显卡杀手类的游戏),也会影响页面性能。浏览器

固然,以上的维度不是划线而治的,它们更可能是犬牙交错的关系。例如在渲染过程当中浏览器反应很慢,有多是脚本写得太烂遭遇性能瓶颈,也有多是显卡杀手游戏占用了过多计算机资源;又如在分析前端资源渲染时,每每要结合网络瀑布图分析资源的获取时间,由于渲染页也是个动态的过程,有些关键资源须要等待,有些则能够在渲染的同时加载。

为何祭出 Performance

Chrome 的开发者工具各有本身的侧重点,如 Network 工具的瀑布图有着资源拉取顺序的详细信息,它的侧重点在于分析网路链路。而 Performance 工具的侧重点则在于前端渲染过程,它拥有帧率条形图、CPU 使用率面积图、资源瀑布图、主线程火焰图、事件总揽等模块,它们和渲染息息相关,善用它们能够清晰地观察整个渲染阶段。不过,你没必要纠结上面提到的模块名,由于在接下来的篇幅里,我会一一介绍他们。

用正确的姿式启动 Performance

打开 Performance

首先咱们打开 Chrome 匿名窗口,在匿名环境下,浏览器不会有额外的插件、用户特性、缓存等影响实验可重复性的因素。接着启动开发者工具,若是你的窗口宽度足够,能够在顶端标签栏的第 5 栏看到 Performance,宽度不够则能够经过右上的 >> 按钮点开更多标签,找到它。

启动 Performance

此时咱们看到的是 Performance 的默认引导页面。其中第一句提示语所对应的操做是当即开始记录当前页面发生的全部事件,点击中止按钮才会中止记录。第二句对应的操做则是重载页面并记录事件,工具会自动在页面加载完毕处于可交互状态时中止记录,二者最终都会生成报告(生成报告须要大量运算,会花费一些时间)。

中止记录

如今,工具已准备好,能够开始分析页面了。

简单页面分析

首先咱们分析一个简单页面从空白页面到渲染完毕的过程。本文全部示例页面都放在下面的仓库里,经过命令克隆并切换到仓库根目录:

git clone git@github.com:pobusama/chrome-preformance-use-demo.git && cd chrome-preformance-use-demo

接着安装依赖包:npm i

最后启动示例页面:npm run demo1

hello world

因为很难把握页面开始渲染的时机,咱们经过第二种 reload 方式收集渲染数据,将 beforeunload -> unload -> Send Request(第一个资源请求) -> load 的过程都记录下来。

在工具自动中止记录后,咱们获得了这样一份报告:

hello world reload 报告

图中划出的 4 个区域分别是:

1:控制面板,用来控制工具的特性。「Network」与「CPU」:分别限制网络和计算资源,模拟不一样终端环境,能够更容易观测到性能瓶颈。「Disable JavaScript samples」选项开启会使工具忽略记录 JS 的调用栈,这个咱们以后会再提到。打开「Enable advanced paint instrumentation」则会详细记录某些渲染事件的细节,这个功能咱们在了解这些事件后再聊。

2:概览面板,其中有描述帧率(FPS)、CPU 使用率、网络资源状况的 3 个图表。帧率是描绘每秒钟渲染多少帧图像的指标,帧率越高则在观感上更流畅。网络状况是以瀑布图的形式呈现,图中观察到各资源的加载时间与顺序。CPU 使用率面积图的实际上是一张连续的堆积柱状图(下面 CPU 面积图放大版为示意图,数据非严谨对应):

概览面板 - CPU 面积图

其纵轴是 CPU 使用率,横轴是时间,不一样的颜色表明着不一样的事件类型,其中:

  • 蓝色:加载(Loading)事件
  • 黄色:脚本运算(Scripting)事件
  • 紫色:渲染(Rendering)事件
  • 绿色:绘制(Painting)事件
  • 灰色:其余(Other)
  • 闲置:浏览器空闲

举例来讲,示意图的第一列:总 CPU 使用率为 18,加载事件(蓝色)和脚本运算事件(黄色)各占了一半(9)。随着时间增长,脚本运算事件的 CPU 使用率逐渐增长,而加载事件的使用率在 600ms 左右降为 0;另外一方面渲染事件(紫色)的使用率先升后降,在 1100ms 左右将为 0。整张图能够清晰地体现哪一个时间段什么事件占据 CPU 多少比例的使用率。

CPU 面积图

3:线程面板,用以观察细节事件,在概览面板缩小观察范围能够看到线程图的细节。其中主线程火焰图是用来分析渲染性能的主要图表。不一样于「正常」火焰图,这里展现的火焰图是倒置的,即最上层是父级函数或应用,越往下则调用栈越浅,最底层的一小格(若是时间维度拉得不够长,看起来像是一小竖线)表示的是函数调用栈顶层。默认状况下火焰图会记录已执行 JS 程序调用栈中的每层函数(精确到单个函数的粒度),很是详细。而开启「Disable JS Samples」后,火焰图只会精确到事件级别(调用某个 JS 文件中的函数是一个事件),忽略该事件下的全部函数调用栈。

主线程火焰图 - 细节

此外,帧线程时序图(Frames)和网络瀑布图(Network)能够从时间维度分别查看绘制出的页面和资源加载状况。

帧线程时序图&网络瀑布图

4:详情面板。前面已经屡次提到事件,我想若是再不解释可能要被寄刀片了。Performance 工具中,全部的记录的最细粒度就是事件。这里的事件不是指 JS 中的事件,而是一个抽象概念,咱们打开主线程火焰图,随意点击一个方块,就能够在详情面板里看到该事件的详情,包括事件名、事件耗时、发起者等信息。举几个例子:Parse HTML 是一种 Loading 事件(蓝色),它表示在在事件时间内,Chrome 正在执行其 HTML 解析算法;Event 是一种 Scripting 事件(黄色),它表示正在执行 JS 事件(例如 click);Paint 是一种绘制事件(绿色),表示 Chrome 将合成的图层绘制出来。

详情面板 - 主线程火焰图中的事件

如下是一些常见事件,有个印象就好,因为每次作性能分析必会跟它们打交道,咱们想不记住他们也难。

详情面板 - 常见的事件

详情面板还有很是重要的一部分就是事件耗时饼状图,它列出了你选择的时间段内,不一样类型事件(加载、脚本运算、渲染、绘制、其余事件、发呆:) )所占的比例和耗费的时间。分析占比同分析 CPU 面积图有相通的意义 —— 究竟是哪一种事件形成了性能瓶颈。

详情面板-饼状图

至此,咱们扫了一遍 Performance 工具的主要功能,虽然没有面面俱到,但足以开启性能分析之旅。接下来咱们分析一个稍微复杂些的动画页面,真正理解这些图表数据如何定位性能问题。

唠叨一下浏览器渲染过程

知晓浏览器的渲染过程对咱们理解分析十分重要,这里简要介绍一下浏览器渲染的过程:

浏览器渲染过程

当渲染首屏时浏览器分别解析 HTML 与 CSS 文件,生成文档对象模型(DOM)与 CSS 对象模型(CSSOM);合并 DOM 与 CSSOM 成为渲染树(Render Tree);计算样式( Style);计算每一个节点在屏幕中的精确位置与大小(Layout);将渲染树按照上一步计算出的位置绘制到图层上(Paint);渲染引擎合成全部图层最终人眼可见(Composite Layers)。

若是改变页面布局,则是先经过 JS 更新 DOM 再经历计算样式到合成图像这个过程。若是仅仅是非几何变化(颜色、visibility、transform),则能够跳过布局步骤。

动画分析

有了上面这些准备,相信你已开始摩拳擦掌了。咱们在示例仓库下跑另一个 Demo: cd chrome-preformance-use-demo && npm run demo2

动画页面初始图

初始状态下,10个小方块会分别上下匀速运动,碰到浏览器边界后原路返回。「Add 10」是增长 10 个这样的小方块,「Substract 10」是减小 10 个,「Stop/Start」暂停/开启全部小方块的运动,「Optimize/Unoptimize」优化/取消优化动画。

浏览器是怎么绘制一帧动画的

在默认状态下,咱们点击左上角的圆记录事件,几秒后咱们能够点击 Performance 中的 Stop 产生分析数据。在概览面板中咱们看到在渡过最初的几百毫秒后,CPU 面积图中各类事件占比按固定周期变化,咱们点取其中一小段观察,在主线程图中可看到一段一段相似事件组。观察几组事件后,咱们发现,这些事件组的组成都是:10 个 Recalculate Style + Layout 的循环 -> Update Layer Tree -> Paint -> Composite Layers。咱们知道,Composite Layers 事件以后,意味着人眼可见新的页面图层,也就是说这样一组事件事后,一帧画面绘制在眼前。

动画页面-分析

咱们点开主线程火焰图的上一栏「Framse」,发现 Composite Layers 事件后不久的虚线处就是下一帧画面出现的节点。

动画页面-Frames

当瓶颈出现时

目前的动画看着没什么毛病,咱们点击十几回「Add 10」按钮,增长方块数,能够看到动画出现了明显的卡顿(若是尚未出现,能够在控制面板里下降 CPU 算力)。这时咱们再次记录性能数据:

动画页面-卡顿

咱们看到报告中有多处醒目的红色,包括帧率图上的大红杠、主线程图中的小角标。再次按照以前的思路,查看主线程的细节,咱们发如今 app.update 函数下发生的 Recalculate Style 和 Layout 事件都出现了警告,提示性能瓶颈的缘由多是强制重排。进入 js 文件查看详细代码,在左栏能够看到消耗了大量时间的代码行呈深黄色,那么这些代码就颇有多是症结所在。

动画页面-卡顿缘由

注意:自动计算代码行运算时间须要取消勾选控制面版的「Disable JavaScript Samples」选项。

那么,这行代码到底有什么问题呢,重排又是什么呢?

再谈重排与重绘

简而言之,重排(reflow)和重绘(repaint)都是改变页面样式的步骤。重排步骤包括 Recalculate Style、Layout、Update Layer Tree 等渲染类型事件,重绘步骤包括 Paint 和 Composite Layers 这些绘制类型事件。重排以后必然会形成重绘,而形成重绘的操做不必定会形成重排。下面列出了一些形成重排或重绘的常见操做,更多操做能够参阅 csstriggers

重排与重绘

因为计算布局须要大量时间,重排的开销远大于重绘,在达到相同效果的状况下,咱们须要尽可能避免重排。举个例子,若是 display: none 和 visibility: hidden 都能知足需求,那么后者更优。

解决瓶颈

再回头看一下咱们的动画 Demo,在 performance 的详情面板中,饼图显示动画的绘制过程当中渲染(重排)占据的大部分的比重,结合代码咱们发现缘由:循环中屡次在刚给 DOM 更新样式位置后,当即经过 offsetTop 获取 DOM 位置。这样的操做会强制启动重排,由于浏览器并不清楚上一个循环内 DOM 有没有改变位置,必须当即从新布局才能计算 DOM 位置。别急,你可能已经注意到了,咱们还有一个「Optimize」按钮。

针对这个问题,咱们的优化方案是将 offsetTop 替换成 style.top,后者虽然取的是上一帧动画的元素位置,但并不影响计算下一帧动画位置,省去了重排获取位置的过程,减小了没必要要的重排。

注意:本示例中,还有一处优化是非 Optimize 的状况下就作了的,就是经过 requestAnimationFrame 函数将一帧循环内全部的样式改动(m.style.top = pos + 'px';)累计在下一次绘制时统一处理。这样作除了优化了样式的写操做,还让咱们更清楚地观察到读操做 offsetTop 引发的问题。

咱们对比一下优化先后的报告:

动画页面-优化
动画页面-优化2

首先从饼图和 CPU 面积图看,Rendering 事件占比下滑,Painting 事件占比上升。而从帧率图和 frames 线程图中分别可看到,帧率明显上升,一帧图像的绘制时间明显降低,意味着动画流畅性大幅提升,优化目的已达到。再看细节,咱们发现绘制一帧动画的事件组中,app.update 函数里没有了 Recalculate style 和 Layout 事件,整个函数执行时间所以显著降低,证实咱们的优化方案起了做用。

回顾与小记

本次关于性能工具的讨论,咱们从影响页面性能的因素谈起,随之引出了 Performance 工具擅长的维度 —— 前端资源渲染。接着,咱们了解了 Performance 工具 4 个主要面板:控制、预览、线程、详情,还有几个实用的图表:帧率条形图、CPU 面积图、主线程火焰图、帧线程时序图、事件耗时饼状图。而后运用它们定位了一个性能问题,并着手解决了该问题。

然而,真实环境下的性能问题更加复杂,在熟练运用 Performance 的同时咱们须要将影响性能的因素熟记于心,这部分的知识和经验须要在实战中长期积累。提高 WEB 性能是个有趣的话题,谷歌 WEB 开发者网站中有不少优质的博客对此展开讨论,不过,浏览器里没有魔法,拿起 Performance 愉快地玩耍吧~

参考

Get Started With Analyzing Runtime Performance

Rendering Tools

Render Tree Construction

Avoid Forced Synchronous Layouts

如何读懂火焰图

网页性能管理详解

相关文章
相关标签/搜索