一文解读“Performance面板”前端性能优化工具基础用法!

本文由体验技术团队董福俊原创。

一、背景

在做前端页面性能优化时,Performance面板是一个必不可少的工具。这个工具比较强大,既可以从全局视角分析整个网页加载情况,又可以从代码细节,挖掘某个具体环节的性能情况。但这个工具的结果可能让人看着感觉难以理解,因为它本质上是将各种运行信息呈现给我们,而不是直接告诉我们问题在哪儿。我们需要将各类信息:浏览器加载过程、框架打包(webpack)、代码编译(tsc、babel)、代码执行(angular)等,综合起来并跟Performance面板上的环节一一对应上,然后才能分析出问题。

本文首先简单介绍Performance面板上的一些高频使用的工具/按钮,然后重点介绍Network面板Main面板的对应关系。

二、Performance面板基本介绍

Performance面板一般用于录制某个操作过程中,浏览器的"内心活动"。包括网络情况、js执行情况、帧渲染情况等。

一般录制步骤是:启动performance记录,到网页上做一段操作,停止录制。然后面板上会展示结果,大概长这样:

下面,一一介绍一下图中这些工具的作用。

1. 操作按钮

  1. 启动/停止:点一下启动performance录制,然后到页面上做特定操作,再点一下结束。
  2. 启动并重载页面:如果要研究页面加载过程的性能,可以直接点这个。(点它 = 点启动 + 刷新页面 + 点停止)
  3. 清空:清空当前分析结果
  4. 保存分析结果:将当前结果保存到本地,以后再用
  5. 加载分析结果:从本地加载某个曾经保存的分析结果,接着分析
  6. 是否保存截屏:这个勾选框需要在启动录制前就决定是否勾选。如果勾上了,那么录制的时候,会把每一帧的可视区截屏记录下来
  7. 是否显示内存:是否显示内存面板,它里面记录着网页内存使用情况的概要
  8. js垃圾回收:一般启动录制之前,多点几下

这些按钮没有特殊之处,多操作几次就能熟练。

2. Network面板

Network面板是会被高频使用的面板 ,它体现了网络下载情况。常常跟后面的 Main面板、Thread pool面板 联合起来分析具体情况。面板中每个色块都代表一个请求,颜色表示请求类型,一般情况下

颜色如果记不住,直接将鼠标挪到色块上的弹窗中,也会体现这个请求的类型。

一般的,一个请求有以下四个阶段:

  1. Queuing and connecting:Queuing指的是浏览器还没有正式处理这个请求,需要排队等待;connection指浏览器跟服务器在建立TCP连接。
  2. Request sent and waiting:表示请求已发送,请等待。也就是HTTP request已经发送到服务端了,等着服务端响应。
  3. Content downloading:表示浏览器已经收到了服务器响应(收到了response head),正在下载response content。
  4. Waiting on main thread:表示response content已经下载完了。但是主线程太繁忙,没有时间执行这个请求的回调函数。

详细介绍见下文 1. 网络请求中的4个环节

3. Frames面板

体现渲染帧率情况,反应页面流畅度。一个格子对应一帧,也就是表示浏览器尝试了一次去绘制屏幕。格子常见有3种颜色:

  1. 绿色块:表示帧被完整渲染并呈现。
  2. 黄色块:表示帧没有完成全部渲染,只呈现了部分内容。
  3. 红色块:表示帧没有完成渲染,被浏览器丢弃。
  4. 白色块:空闲帧,表示当前没有需要执行的渲染任务了。

注意:格子颜色与帧的时间长短无关。

4. Animations面板

体现合成器线程的情况,它主要反应了我们使用的css animations或transition的执行情况。 每个色块对应着一次正在运行的animations或transition,并且点击这个色块,能在摘要面板(summary)中看到着一次执行的相关信息(特别是被执行的DOM节点会体现出来)

5. Timings面板

记录一些时间信息,包括页面加载的关键时间节点。并能体现出如console.time、performance.mark 等。 (PS:在代码中写的performance.mark会体现为这里的一条条细线,而console.time会体现为这里的一个个色块。我们在代码的关键节点增加一些mark、time,就能在分析面板中直观的了解当前的页面加载进度)

6. Layout shifts面板

记录布局偏移情况。布局偏移是指页面上一个可见元素的位置发生了移动,就会被记录在这里。每次偏移都对应截图中的一个小点。

可见元素的位置发生移动,会引起视觉不适,所以一般要尽量避免布局偏移。而常见会引发布局偏移的动作有:动态插入一些内容,一些DOM操作等。

7. Main面板

记录浏览器主线程的活动,我们常说js是单线程的,指的就是js运行在这个主线程上。每个色块对应一个任务(注意:不是宏任务/微任务的那个任务)。

色块从上到下是包含关系,指的是:上一层的色块(任务)是由下一层的若干个子色块(子任务)组合而成的。

色块从左到右是并列关系,指的是:并列的若干个子色块,是从左到右执行的。(左边色块如果有子色块,那么会先把子色块执行完成后,再执行右边色块)。

详细介绍见下文 3. 主线程对请求结果的处理

8. Thread pool面板

记录worker线程的执行情况。worker线程主要用于执行耗时、计算密集型且可以并行化的后台任务。

Thread pool是一个线程池,这个池子中开出来的线程数,是由浏览器根据任务类型、系统资源、硬件能力等因素动态调整的结果。一般情况下,开出来的线程数会接近或略多于CPU的核心数。网络请求的结果,会放在某个线程中进行解析和编译。

详细介绍见下文 2. worker线程对请求结果的解析编译

三、Network面板、Main面板及Thread pool面板

一个静态资源加载过程的各个阶段中,网络传输阶段体现在Network面板,内容解析编译阶段体现在worker线程,资源的处理阶段体现在主线程。联合分析三个面板就能掌握详细的静态资源加载全流程。

1. 网络请求中的4个环节

前面对Network面板的介绍中提到,一个HTTP请求会经过4个阶段:Queuing and connecting、Request sent and waiting、Content downloading、Waiting on main thread。这4个阶段从HTTP报文的视角,会比较好理解。

1.1 "一问一答"的HTTP请求

众所周知,HTTP请求是一问一答的模式(下图右半部分),HTTP报文是字符串格式(下图左半部分)。

我们在代码中写的 <script>、ajax、fetch,都会让浏览器去发起一个HTTP请求,这个请求就会经过以下4个阶段:

  • Queuing and connecting

浏览器知道了要发请求,但它正忙着建立Tcp链接或别的事情,所以这个请求要先等一等。

  • Request sent and waiting

浏览器终于开始处理这个请求了。它按HTTP报文格式,将Request Head发到Server,然后等Server的回复。此刻Server正忙着解析这个请求,思考该如何回答这个问题(例如从服务器磁盘上读取某个文件来响应这个请求)。这个过程中浏览器一直在等待,双方在TCP层会通过Keep-Alive报文(如果有必要的话)来保持链接。 (PS:当服务器准备好响应内容后,它会按HTTP报文格式的规定,先后返回Response Head和Response Body给Browser)

  • Content downloading

表示当前浏览器已经收到Server返回的Response Head,它正忙着接收Response Body。浏览器会在worker线程中解析编译Response Body,不占用主线程。只有当整个Response Body完整的下载解析完成后,才将解析好的结果移交给主线程去执行。

  • Waiting on main thread

浏览器主线程收到了worker线程移交过来的解析结果,但主线程太忙,没有时间处理。(也就是没有时间触发回调函数)

1.2 HTTP/1.1及HTTP/2

页面初始化时往往会有大量请求要发出去,这就导致请求往往会卡在第1个阶段 Queuing and connecting。

  • HTTP/1.1

在HTTP/1.1协议中,请求是一问一答的模式,所以前一个请求没有被回答时,后一个请求就不能发出去(否则收到响应后会搞不清楚回答的是哪个请求)。浏览器为了提升请求速度,往往会跟Server同时建立多个TCP链接。

HTTP协议是建立在TCP链接之上的,而TCP链接是靠四元组(客户端IP+客户端端口号+服务端IP+服务端端口号)来区分的。在我们讨论的场景中,浏览器是客户端,Server是服务端,一般客户端和服务端的IP是唯一的,而服务端可能要同时为多个客户端服务,所以服务端端口号比较珍贵。那么,浏览器为了跟Server同时建立多条TCP链接,就只能选择在客户端上开多个端口号来建立多条TCP链接。

所以,浏览器一般对同一个域名 会建立一定数量 (见 说明1 ** **的TCP链接(浏览器开多个TCP端口,服务端只开1个TCP端口)。但这也只是缓解请求排队现象,如果请求数超过TCP链接数,那么靠后的请求还是得依次排队等待可用的TCP链接。

有些场景下,为了进一步提升请求速度,我们也可以采用域名分片 方式来提升下载速度,即:将静态资源放到多个域名下,这样浏览器就能开更多的TCP链接去下载资源。但很显然,HTTP/1.1协议慢就慢在它必须按顺序一问一答的去处理HTTP请求,前一个请求没有响应完,后一个请求就不能发出去,这个问题叫 队头阻塞。这就好像你去超市买1瓶矿泉水,到收银台买单时发现队伍排的很长,队头第一个人有一满购物车的东西等着扫码。此时即使再多开几个收银台,也只能缓解排队。为了根治这个问题,HTTP/2中引入了多路复用机制。

  • HTTP/2

为了根治队头阻塞问题,HTTP/2协议引入了多路复用机制,它允许同一个TCP链接上可以同时发多个HTTP请求。也就是说浏览器可以一次性把多个HTTP请求的request head都发出去,并允许大家的响应交叉返回。所以这种情况下,浏览器一般只需要建立1个TCP链接即可。

从以上分析可以看出这几点:

  1. HTTP/1.1协议下,同一个域名下的静态资源,才会互相竞争网络。
  2. HTTP/2协议下,同一个域名下,多个HTTP请求能并发。

2. worker线程对请求结果的解析编译

通过网络请求,浏览器拿到的是字符串形态的代码,它们需要被解析、编译之后才能执行。前面介绍Thread pool面板时提到过,worker线程一般用于执行耗时、计算密集型且可以并行化的后台任务。而网络请求结果的解析和编译,正好适合放在worker中进行。

例如,我们在代码中写了一个<script>标签,浏览器会通过HTTP请求去下载这个js文件。请求的Response Body中就是js文件的内容,但内容是字符串形态的js代码,它需要先解析编译才能执行。

  1. 解析:对字符串形态的js代码进行语法分析,产生AST(抽象语法树)
  2. 编译:V8引擎的编译器根据AST,生成字节码或机器码
  3. 执行:V8引擎逐条运行这些字节码或机器码

注意:浏览器会在 worker线程中完成解析&编译环节,然后将结果移交给主线程去执行。PS:在Thread pool面板的worker中看到大量的Parse and compile块,就是解析和编译

(PS:通过点击Thread pool worker中的色块,Summary面板会联动展示相关信息,可以看出这个色块是在解析哪个静态资源)

由于Thread pool线程池里面有多个线程(worker),所以每个静态资源很容易分配到一个worker进行解析。而且现代浏览器一般是流式解析和编译 ,也就是上图中的Streaming compile task,它表现为:每收到一个数据块就解析一个数据块

一般的,解析和编译往往不是速度瓶颈,网络下载才是。现代电脑硬件和浏览器能力,往往能迅速完成对数据块的解析和编译,大多数时间都消耗在等待其余的数据块。

3. 主线程对请求结果的处理

当worker线程完成对解析、编译之后,其产生的字节码或机器码将会被移交到主线程中去执行(如果是webworker加载的js,则会被移交到对应的webworker线程中),我们看到的Main面板就是反应主线程的活动情况,这个图一般叫火焰图

面板中task下面的Evaluate script和Evaluate module(声明了type="module"的script)色块就是在执行代码。一般在Evaludate色块下面的(anonymouse)色块表示的是顶层代码,可以理解为一个超大的匿名函数,将所有待执行代码包裹其中。

另外,在图中我们还看到Evaluate script下面出现了Compile script,这是因为V8引擎的惰性编译 策略。worker中的编译动作并不会将整个代码中的所有函数一次性全部编译掉,它通常只编译顶层代码和可能会立即要被调用的函数,这样能避免编译非必要代码。所以在Evaluate script的过程中,如果主线程要执行一个尚未被编译的函数,那么它会通过即时编译器(JIT) 来编译这个函数。另外,有一些代码(例如内联script)只能在主线程中编译。同样的,Evaluate module下面会出现Compile code,可能是因为它要执行一个从别的module引用过来的函数,需要先JIT编译。

色块代表着任务,任务的种类很多,但大多数的色块表示的就是一个函数,色块的名字就是函数的名字。所以色块的上下顺序就表示函数见的调用。但是色块上很容易出现一个一些奇怪的名字,例如:

  1. anonymouse:匿名函数,代码中的各种回调函数,往往都是匿名函数(也有使用具名函数当回调的,那么色块就会显示函数名)。
  2. 一些英文字母:例如 c.O、L、U等。当我们针对生产环境抓包时往往会遇到这些。这是因为代码打包时往往会做代码混淆,将原始代码中的变量名、函数名换成无意义的短名称。
  3. 一些数字:webpack打包时,会将多个js文件打成同一个chunk。而在chunk中,每个文件都会被包裹在一个匿名函数中,并放在一个对象中同时生成一个数字作为key。

一般的,奇怪的色块名字,基本都是打包工程处理的结果。如果想要看到这些函数的真实名字,则还需要找到打包产生的SourceMap文件,导入到浏览器中。一般情况下,我们可以同时对本地代理和生产环境录制performance,两相对比之下,也大致能搞清楚各个色块大概是在做什么。

如果我们针对本地代理localhost录制performance,则往往能看到真实的函数名。但仔细对比,有时会发现,有一些被调用的函数并没有出现在色块里面。比如:fn1调用了fn2、fn3、fn4,但色块fn1下只有fn2和fn3两个色块,fn4不见了!这可能是因为火焰图是采样图,Chrome默认1000Hz(每1ms采集一次调用栈),所以如果一个函数的执行时长 <0.1ms,那么这个函数可能被漏采样了,所以就没有它对应的色块。当然,也有可能单纯就是这个函数没有被执行。

四、总结

Performance面板将浏览器的"内心活动"都录制下来展现给我们,但是从录制结果中找到问题原因的过程,是复杂且需要大量综合信息支撑的。其中,对每个面板所呈现的信息进行理解是最基本的前提,只有在此基础之上,我们才能结合我们的工程配置、代码情况 以及各种前端相关技术知识,来对我们的页面有个综合诊断,再然后才是拟定优化方案并实施阶段。如果前面的基础都没有找对方向,那么最后的优化结果也只能是各种碰运气。在实战中,录制结果也往往是动态的,即使是同一个页面,在不同时间、不同CPU负荷、不同网络情况下的结果都可能会有不同,所以需要多次录制并在不同结果中找到相同的问题点,进行重点优化。

在下一篇文章《聊聊Performance面板2------网页加载实战演练》中,我们将结合一个实战案例来说明具体的分析过程。

五、附录

说明1. HTTP/1.1下浏览器对同一个域名同时建立的TCP连接数

坊间传闻,一般chrome默认是最大6个TCP连接。

但真实情况中,有见过10个TCP连接的,如下图所示:(在DevTools的网络面板中,将connection ID列放出来,每个connection ID对应一个TCP连接)

对于这种显现给,也有另一种说法认为,是在开启devtools之后,浏览器会临时突破连接限制(观察者效应)导致的,但具体原因尚不明确。

关于 OpenTiny

欢迎加入 OpenTiny 开源社区。添加微信小助手:opentiny-official 一起参与交流前端技术~

OpenTiny 官网https://opentiny.design
OpenTiny 代码仓库https://github.com/opentiny
TinyVue 源码https://github.com/opentiny/tiny-vue
TinyEngine 源码: https://github.com/opentiny/tiny-engine

欢迎进入代码仓库 Star🌟TinyEngine、TinyVue、TinyNG、TinyCLI、TinyEditor~

如果你也想要共建,可以进入代码仓库,找到 good first issue 标签,一起参与开源贡献~

相关推荐
HUMHSX18 分钟前
Vue 项目启动全流程解析:从入口文件到全局指令注册与页面渲染
前端·javascript·vue.js
有颜有货29 分钟前
PMC生产排产的4种算法,一次讲清
java·服务器·前端
小虎牙00732 分钟前
Android kotlin图片库Coil源码详解
android·前端
ai产品老杨33 分钟前
多路摄像头AI分析性能优化指南
人工智能·性能优化
随风一样自由41 分钟前
【前端领域】前端开发核心应用场景与落地实践
前端·前端框架
an317421 小时前
弹窗数据流设计的两种高阶架构实践
前端·vue.js·架构
谢尔登1 小时前
【React】 状态管理方案
前端·react.js·前端框架
用户2136610035722 小时前
Vue商品详情与放大镜组件
前端·javascript
半个落月2 小时前
从Tapas小Demo理清localStorage、事件与this
前端·javascript
李明卫杭州2 小时前
Vue2 中 v-model 处理不同数据结构的技巧
前端·javascript·vue.js