“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 官网opentiny.design
OpenTiny 代码仓库github.com/opentiny
TinyVue 源码github.com/opentiny/ti...
TinyEngine 源码: github.com/opentiny/ti...

欢迎进入代码仓库 Star🌟TinyEngine、TinyVue、TinyNG、TinyCLI、TinyEditor~ 如果你也想要共建,可以进入代码仓库,找到 good first issue 标签,一起参与开源贡献~

相关推荐
该用户已不存在5 分钟前
人人都爱的开发工具,但不一定合适自己
前端·后端
ZzMemory17 分钟前
JavaScript 类数组:披着数组外衣的 “伪装者”?
前端·javascript·面试
梁萌28 分钟前
前端UI组件库
前端·ui
鲸渔32 分钟前
CSS高频属性速查指南
前端·css·css3
小高00732 分钟前
🌐AST(抽象语法树):前端开发的“代码编译器”
前端·javascript·面试
蓝易云33 分钟前
Git stash命令的详细使用说明及案例分析。
前端·git·后端
GIS瞧葩菜35 分钟前
Cesium 中拾取 3DTiles 交点坐标
前端·javascript·cesium
Allen Bright35 分钟前
【JS-7-ajax】AJAX技术:现代Web开发的异步通信核心
前端·javascript·ajax
轻语呢喃40 分钟前
Mock : 没有后端也能玩的虚拟数据
前端·javascript·react.js
Dnui_King44 分钟前
Oracle 在线重定义
java·服务器·前端