This commit is contained in:
louzefeng
2024-07-09 18:38:56 +00:00
parent 8bafaef34d
commit bf99793fd0
6071 changed files with 1017944 additions and 0 deletions

View File

@@ -0,0 +1,162 @@
<audio id="audio" title="加餐一浏览上下文组如何计算Chrome中渲染进程的个数" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/cd/59/cda88e0850cd772520441adf9264e159.mp3"></audio>
你好,我是李兵。
在留言区经常有朋友问到如何计算Chrome中渲染进程个数的问题那么今天我就来完整地解答这个问题。
在前面“[04 | 导航流程](https://time.geekbang.org/column/article/117637)”这一讲中我们介绍过了,在默认情况下,如果打开一个标签页,那么浏览器会默认为其创建一个渲染进程。不过我们在“[04 | 导航流程](https://time.geekbang.org/column/article/117637)”中还介绍了同一站点的概念,如果从一个标签页中打开了另一个新标签页,当新标签页和当前标签页属于同一站点的话,那么新标签页会复用当前标签页的渲染进程。
具体地讲,如果我从极客邦(www.geekbang.org) 的标签页中打开新的极客时间(time.geekbang.org) 标签页,由于这两个标签页属于同一站点(相同协议、相同根域名)所以他们会共用同一个渲染进程。你可以看下面这张Chrome的任务管理器截图
<img src="https://static001.geekbang.org/resource/image/f8/5c/f87168a79df0b87a08b243937f53545c.png" alt="">
观察上图我们可以看到极客邦官网和极客时间标签页都共用同一个渲染进程该进程ID是84748。
不过如果我们分别打开这两个标签页,比如先打开极客邦的标签页,然后再新建一个标签页,再在这个新标签页中打开极客时间,这时候我们可以看到这两个标签页分别使用了两个不同的渲染进程。你可以参看下图:
<img src="https://static001.geekbang.org/resource/image/34/f9/34815ee3a8d5057d39ebb6f871fbf0f9.jpg" alt="">
那么到了这里你一定会很好奇既然都是同一站点为什么从A标签页中打开B标签页就会使用同一个渲染进程而分别打开这两个标签页又会分别使用不同的渲染进程
## 标签页之间的连接
要搞清楚这个问题,我们要先来分析下浏览器标签页之间的连接关系。
我们知道浏览器标签页之间是可以通过JavaScript脚本来连接的通常情况下有如下几种连接方式
**第一种是通过`&lt;a&gt;`标签来和新标签建立连接**,这种方式我们最熟悉,比如下面这行代码是从极客邦标签页里面拷贝过来的:
```
&lt;a href=&quot;https://time.geekbang.org/&quot; target=&quot;_blank&quot; class=&quot;&quot;&gt;极客时间&lt;/a&gt;
```
这是从极客邦官网中打开极客时间的链接点击该链接会打开新的极客时间标签页新标签页中的window.opener的值就是指向极客邦标签页中的window这样就可以在新的极客时间标签页中通过opener来操作上个极客邦的标签页了。这样我们可以说这两个标签页是有连接的。
另外,**还可以通过JavaScript中的window.open方法来和新标签页建立连接**,演示代码如下所示:
```
new_window = window.open(&quot;http://time.geekbang.org&quot;)
```
通过上面这种方式可以在当前标签页中通过new_window来控制新标签页还可以在新标签页中通过window.opener来控制当前标签页。所以我们也可以说如果从A标签页中通过window.open的方式打开B标签页那么A和B标签页也是有连接的。
其实通过上述两种方式打开的新标签页不论这两个标签页是否属于同一站点他们之间都能通过opener来建立连接所以他们之间是有联系的。在WhatWG规范中把这一类具有相互连接关系的标签页称为**浏览上下文组( browsing context group)。**
既然提到浏览上下文组就有必要提下浏览上下文通常情况下我们把一个标签页所包含的内容诸如window对象历史记录滚动条位置等信息称为浏览上下文。这些通过脚本相互连接起来的浏览上下文就是浏览上下文组。如果你有兴趣可以参开下[规范文档](https://html.spec.whatwg.org/multipage/browsers.html#groupings-of-browsing-contexts)。
也就是说如果在极客邦的标签页中通过链接打开了多个新的标签页不管这几个新的标签页是否是同一站点他们都和极客邦的标签页构成了浏览上下文组因为这些标签页中的opener都指向了极客邦标签页。
**Chrome浏览器会将浏览上下文组中属于同一站点的标签分配到同一个渲染进程中**,这是因为如果一组标签页,既在同一个浏览上下文组中,又属于同一站点,那么它们可能需要在对方的标签页中执行脚本。因此,它们必须运行在同一渲染进程中。
现在我们清楚了浏览器是怎么分配渲染进程的了,接下来我们就可以来分析文章开头提的那个问题了:
>
既然都是同一站点为什么从A标签页中打开B标签页就会使用同一个渲染进程 而分别打开这两个标签页,又会分别使用不同的渲染进程?
首先来看第一种,在极客邦标签页内部通过链接打开极客时间标签页,那么极客时间标签页和极客邦标签页属于同一个浏览上下文组,且它们属于同一站点,所以浏览器会将它们分配到同一个渲染进程之中。
而第二种情况就简单多了,因为第二个标签页中并没有第一个标签页中的任何信息,第一个标签页也不包含任何第二个标签页中的信息,所以他们不属于同一个浏览上下文组,因此即便他们属于同一站点,也不会运行在同一个渲染进程之中。下面是我画的计算标签页的流程图,你可以参考下:
<img src="https://static001.geekbang.org/resource/image/cb/b6/cbc89902f5ce12420101246c4a227cb6.jpg" alt="">
## 一个“例外”
好了现在我们清楚了Chrome浏览器为标签页分配渲染进程的策略了
1. **如果两个标签页都位于同一个浏览上下文组,且属于同一站点,那么这两个标签页会被浏览器分配到同一个渲染进程中。**
1. **如果这两个条件不能同时满足,那么这两个标签页会分别使用不同的渲染进程来渲染。**
现在你可以想一下如果从A标签页中打开B标签页那我们能肯定A标签页和B标签页属于同一浏览上下文组吗
答案是“不能”,下面我们就来看个例子,在“[04 | 导航流程](https://time.geekbang.org/column/article/117637)”的留言区中ID为“芳华年月”的朋友就提出了这样的一个问题
>
请问老师,[https://linkmarket.aliyun.com](https://linkmarket.aliyun.com) 内新开的标签页都是新开一个渲染进程,能帮忙解释下吗?
我们先来复现下“芳华年月”所描述的现象首先打开linkmarket.aliyun.com这个标签页再在这个标签页中随便点击两个链接然后就打开了两个新的标签页了如下图所示
<img src="https://static001.geekbang.org/resource/image/87/44/8727a2cef7bc8bc2023a37d6368bb344.png" alt="">
我通过A标签页中的链接打开了两个新标签页B和C而且我们也可以看出来A、B、C三个标签页都属于同一站点正常情况下它们应该共用同一个渲染进程不过通过上图我们可以看出来A、B、C三个标签页分别使用了三个不同的渲染进程。
既然属于同一站点,又不在同一个渲染进程中,所以可以推断这三个标签页不属于同一个浏览上下文组,那么我们接下来的分析思路就很清晰了:
1. 首先验证这三个标签页是不是真的不在同一个浏览上下文组中;
1. 然后再分析它们为什么不在同一浏览上下文组。
为了验证猜测我们可以通过控制台来看看B标签页和C标签标签页的opener的值结果发现这两个标签页中的opener的值都是null这就确定了B、C标签页和A标签页没有连接关系当然也就不属于同一浏览上下文组了。
验证了猜测,接下来的我们就是来查查,阿里的这个站点是不是采用了什么特别的手段,移除了这两个标签页之间的连接关系。
我们可以看看实现链接的HTML文件如下图所示<br>
<img src="https://static001.geekbang.org/resource/image/ec/7e/ec3c6414a0e6eff3a04cfa7ec9486f7e.jpg" alt="">
通过上图我们可以发现a链接的rel属性值都使用了noopener 和 noreferrer通过noopener我们能猜测得到这两个值是让被链接的标签页和当前标签页不要产生连接关系。
通常将noopener的值引入rel属性中就是告诉浏览器通过这个链接打开的标签页中的opener值设置为null引入noreferrer是告诉浏览器新打开的标签页不要有引用关系。
好了到了这里我们就知道了通过linkmarket.aliyun.com标签页打开新的标签页要使用单独的一个进程是因为使用了rel= noopener的属性所以新打开的标签页和现在的标签页就没有了引用关系当然它们也就不属于同一浏览上下文组了。这也同时解答了“芳华年月”所提出的问题。
## 站点隔离
上面我们都是基于标签页来分析渲染进程的,不过我在“[35安全沙箱](https://time.geekbang.org/column/article/155183)”中介绍过了目前Chrome浏览器已经默认实现了站点隔离的功能这意味着标签页中的iframe也会遵守同一站点的分配原则如果标签页中的iframe和标签页是同一站点并且有连接关系那么标签页依然会和当前标签页运行在同一个渲染进程中如果iframe和标签页不属于同一站点那么iframe会运行在单独的渲染进程中。
我们先来看下面这个具体的例子吧:
```
&lt;head&gt;
&lt;title&gt;站点隔离:demo&lt;/title&gt;
&lt;style&gt;
iframe {
width: 800px;
height: 300px;
}
&lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;div&gt;&lt;iframe src=&quot;iframe.html&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;
&lt;div&gt;&lt;iframe src=&quot;https://www.infoq.cn/&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;
&lt;div&gt;&lt;iframe src=&quot;https://time.geekbang.org/&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;
&lt;div&gt;&lt;iframe src=&quot;https://www.geekbang.org/&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;
```
在Chrome浏览器中打开上面这个标签页然后观察Chrome的任务管理我们会发现这个标签页使用了四个渲染进程如下图所示
<img src="https://static001.geekbang.org/resource/image/47/1d/4762ab5be219271ff3e26c1f4c4f521d.png" alt="">
结合上图和HTML代码我们可以发现由于InfoQ、极客邦两个iframe与父标签页不属于同一站点所以它们会被分配到不同的渲染进程中而iframe.html和源标签页属于同一站点所以它会和源标签页运行在同一个渲染进程中。下面是我画的计算iframe使用渲染进程数目的流程图你可以对照着参考下
<img src="https://static001.geekbang.org/resource/image/a1/0e/a13f917f227e85102998b3bfe38b4e0e.jpg" alt="">
## 总结
好了,本节的内容就介绍到这里,下面我来总结下本文的主要内容:
首先我们使用了两种不同的方式打开两个标签页第一种是从A标签页中通过链接打开了B标签页第二种是分别打开A和B标签页这两种情况下的A和B都属于同一站点。
通过Chrome的任务管理器我们发现虽然A标签页和B标签页都属于同一站点不过通过第一种方式打开的A标签页和B标签页会共用同一个渲染进程而通过第二种方式打开的两个标签页却分别使用了两个不同的渲染进程。
这是因为使用同一个渲染进程需要满足两个条件首先A标签页和B标签页属于同一站点其次A标签页和B标签页需要有连接关系。
接着我们分析了一个“例外”如果在链接中加入了rel=noopener属性那么通过链接打开的新标签页和源标签页之间就不会建立连接关系了。
最后我们还分析了站点隔离对渲染进程个数的影响如果A标签页中的iframe和A标签页属于同一站点那么该iframe和A标签页会共用同一个渲染进程如果不是则该iframe会使用单独的渲染进程。
好了,到了这里相信你已经会计算渲染进程的个数了。
在最后我们还要补充下同源策略对同一站点的限制虽然Chrome会让有连接且属于同一站点的标签页运行在同一个渲染进程中不过如果A标签页和B标签页属于同一站点却不属于同源站点那么你依然无法通过opener来操作父标签页中的DOM这依然会受到同源策略的限制。
简单地讲极客邦和极客时间属于同一站点但是他们并不是同源的因为同源是需要相同域名的虽然根域名geekbang.org相同但是域名却是不相同的一个是time.geekbang.org一个是www.geekbang.org 因此浏览器判断它们不是同源的所以依然无法通过time.geekbang.org标签页中的opener来操作www.geekbang.org中的DOM。
## 思考题
那么今天留给你的思考题是你认为Chrome为什么使用同一站点划分渲染进程而不是使用同源策略来划分渲染进程
欢迎在留言区与我分享你的想法,也欢迎你在留言区记录你的思考过程。感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给更多的朋友。

View File

@@ -0,0 +1,166 @@
<audio id="audio" title="加餐三加载阶段性能使用Audits来优化Web性能" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/da/0e/da3e6177586fd36efbff15e8c935260e.mp3"></audio>
你好,我是李兵。
作为一名前端工程师除了需要编写功能性的代码以外我们还需要关注Web应用的性能问题我们应该有能力让我们的Web应用占用最小的资源并以最高性能运行这也是前端工程师进阶的必要能力。既然性能这么重要那么我们今天要来聊聊Web性能问题。
## 到底什么是Web性能?
我们看下wiki对Web 性能的[定义](https://en.wikipedia.org/wiki/Web_performance)
>
Web 性能描述了Web应用在浏览器上的加载和显示的速度。
因此当我们讨论Web性能时其实就是讨论Web应用速度关于Web应用的速度我们需要从两个阶段来考虑
- 页面加载阶段;
- 页面交互阶段。
在本文中,我们会将焦点放到第一个阶段:页面加载阶段的性能,在下篇文章中,我们会来重点分析页面交互阶段的性能。
## 性能检测工具Performance vs Audits
要想优化Web的性能我们就需要有监控Web应用的性能数据那怎么监控呢
如果没有工具来模拟各种不同的场景并统计各种性能指标那么定位Web应用的性能瓶颈将是一件非常困难的任务。幸好Chrome为我们提供了非常完善的性能检测工具**Performance**和**Audits**它们能够准确统计页面在加载阶段和运行阶段的一些核心数据诸如任务执行记录、首屏展示花费的时长等有了这些数据我们就能很容易定位到Web应用的**性能瓶颈** 。
首先Performance非常强大因为它为我们提供了非常多的运行时数据利用这些数据我们就可以分析出来Web应用的瓶颈。但是要完全学会其使用方式却是非常有难度的其难点在于这些数据涉及到了特别多的概念而这些概念又和浏览器的系统架构、消息循环机制、渲染流水线等知识紧密联系在了一起。
相反Audtis就简单了许多它将检测到的细节数据隐藏在背后只提供给我们一些直观的性能数据同时还会给我们提供一些优化建议。
Perfomance能让我们看到更多细节数据但是更加复杂Audits就比较智能但是隐藏了更多细节。为了能够让你循序渐进地理解内容所以本节我们先从简单的Audits入手看看如何利用它来检测和优化页面在加载阶段的性能然后在下一节我们再来分析Perfomance。
## 检测之前准备工作
不过在检测Web的性能指标之前我们还要配置好工作环境具体地讲你需要准备以下内容
- 首先准备Chrome Canary版的浏览器Chrome Canary是采用最新技术构建的它的开发者工具和浏览器特性都是最新的所以我推荐你使用Chrome Canary来做性能分析。当然你也可以使用稳定版的Chrome。
- 然后我们需要在Chrome的隐身模式下工作这样可以确保我们安装的扩展、浏览器缓存、Cookie等数据不会影响到检测结果。
## 利用Audits生成Web性能报告
环境准备好了之后,我们就可以生成站点在加载阶段的性能报告了,这里我们可以拿[B站](https://www.bilibili.com/index.html?redirectFrom=h5)作为分析的列子。
- 首先我们打开浏览器的隐身窗口Windows系统下面的快捷键是Control+Shift+NMac系统下面的快捷键是Command+Shift+N。
- 然后在隐身窗口中输入B站的网站。
- 打开Chrome的开发者工具选择Audits标签。
最终打开的页面如下图所示:
<img src="https://static001.geekbang.org/resource/image/f4/09/f47e598b2fe371e0af067c74756a8909.png" alt="">
观察上图中的Audits界面我们可以看到在生成报告之前我们需要先配置Audits配置模块主要有两部分组成一个是**监测类型(Categories)**,另外一个是**设备类型(Device)**。
**监控类型(Categories)是指需要监控哪些内容**,这里有五个对应的选项,它们的功能分别是:
- 监测并分析Web性能(**Performance**)
- 监测并分析PWA(**Progressive Web App**)程序的性能;
- 监测并分析Web应用是否采用了最佳实践策略(**Best practices**)
- 监测并分析是否实施了无障碍功能(**Accessibility**)[无障碍功能](https://developers.google.com/web/fundamentals/accessibility?utm_source=lighthouse&amp;utm_medium=devtools)让一些身体有障碍的人可以方便地浏览你的Web应用。
- 监测并分析Web应用是否采实施了SEO搜素引擎优化(**SEO**)。
本文我们只需要关注Web应用的加载性能所以勾选第一个Performance选项就可以了。
再看看**设备(Device)部分**它给了我们两个选项Moblie选项是用来模拟移动设备环境的另外一个Desktop选项是用来模拟桌面环境的。这里我们选择移动设备选项因为目前大多数流量都是由移动设备产生的所以移动设备上的Web性能显得更加重要。
配置好选项之后,我们就可以点击最上面的生成报告(Generate report)按钮来生成报告了。
## 解读性能报告
点击生成报告的按钮之后我们大约需要等待一分钟左右Audits就可以生成最终的分析报告了如下图所示
<img src="https://static001.geekbang.org/resource/image/c0/22/c0420197cc60fb91af2f38903afc8022.png" alt="">
观察上图的分析报告中间圆圈中的数字表示该站点在加载过程中的总体Web性能得分总分是100分。我们目前的得分为46分这表示该站点加载阶段的性能还有很大的提升空间。
Audits除了生成性能指标以外还会分析该站点并提供了很多优化建议我们可以根据这些建议来改进Web应用以获得更高的得分进而获得更好的用户体验效果。
既能分析Web性能得分又能给出优化建议所以Audits的分析报告还是非常有价值的那么接下来我们就来解读下Audits生成的性能报告。
报告的第一个部分是**性能指标(Metrics)**,如下图所示:
<img src="https://static001.geekbang.org/resource/image/d2/26/d27cde1230afbabf6f914ee987c15026.png" alt="">
观察上图我们可以发现性能指标下面一共有六项内容这六项内容分别对应了从Web应用的加载到页面展示完成的这段时间中各个阶段所消耗的时长。在中间还有一个View Trace按钮点击该按钮可以跳转到Performance标签并且查看这些阶段在Performance中所对应的位置。最下方是加载过程中各个时间段的屏幕截图。
报告的第二个部分是**可优化项(Opportunities)**,如下图所示:
<img src="https://static001.geekbang.org/resource/image/27/88/275dfab0e15ccf4f59e909e352197b88.png" alt="">
这些可优化项是Audits发现页面中的一些可以直接优化的部分你可以对照Audits给的这些提示来优化你的Web应用。
报告的第三部分是**手动诊断(Diagnostics)**,如下图所示:
<img src="https://static001.geekbang.org/resource/image/1b/82/1bd988dde8315b1d286a5d72c0244d82.png" alt="">
在手动诊断部分采集了一些可能存在性能问题的指标这些指标可能会影响到页面的加载性能Audits把详情列出来并让你依据实际情况来手动排查每一项。
报告的最后一部分是**运行时设置(Runtime Settings)**,如下图所示:
<img src="https://static001.geekbang.org/resource/image/1a/7a/1a48f900ad3ce35371b92431d984507a.png" alt="">
观察上图这是运行时的一些基本数据如果选择移动设备模式你可以看到发送网络请求时的User Agent 会变成设备相关信息,还有会模拟设备的网速,这个体现在网络限速上。
## 根据性能报告优化Web性能
现在有了性能报告接下来我们就可以依据报告来分析如何优化Web应用了。最直接的方式是想办法提高性能指标的分数而性能指标的分数是由六项指标决定的它们分别是
1. 首次绘制(First Paint)
1. 首次有效绘制(First Meaningfull Paint)
1. 首屏时间(Speed Index)
1. 首次CPU空闲时间(First CPU Idle)
1. 完全可交互时间(Time to Interactive)
1. 最大估计输入延时(Max Potential First Input Delay)。
那么接下来我会逐一分析六项指标的含义,并讨论如何提升这六项指标的数值。这六项都是页面在加载过程中的性能指标,所以要弄明白这六项指标的具体含义,我们还得结合页面的加载过程来分析。一图胜过千言,我们还是先看下面这张页面从加载到展示的过程图:
<img src="https://static001.geekbang.org/resource/image/70/99/7041b4d913a12d4d53041e8ed8b30499.png" alt="">
观察上图的页面加载过程,我们发现,在渲染进程确认要渲染当前的请求后,渲染进程会创建一个空白页面,我们把创建空白页面的这个时间点称为**First Paint**,简称**FP**。
然后渲染进程继续请求关键资源,我们在《[25页面性能如何系统地优化页面](https://time.geekbang.org/column/article/143889)》这节中介绍过了关键资源并且知道了关键资源包括了JavaScript文件和CSS文件因为关键资源会阻塞页面的渲染所以我们需要等待关键资源加载完成后才能执行进一步的页面绘制。
上图中bundle.js是关键资源因此需要完成加载之后渲染进程才能执行该脚本然后脚本会修改DOM引发重绘和重排等一系列操作当页面中绘制了第一个像素时我们把这个时间点称为**First Content Paint**,简称**FCP**。
接下来继续执行JavaScript脚本当首屏内容完全绘制完成时我们把这个时间点称为**Largest Content Paint**,简称**LCP**。
在FCP和LCP中间还有一个FMP这个是首次有效绘制由于FMP计算复杂而且容易出错现在不推荐使用该指标所以这里我们也不做过多介绍了。
接下来JavaScript脚本执行结束渲染进程判断该页面的DOM生成完毕于是触发DOMContentLoad事件。等所有资源都加载结束之后再触发onload事件。
好了,以上就是页面在加载过程中各个重要的时间节点,了解了这些时间节点,我们就可以来聊聊性能报告的六项指标的含义并讨论如何优化这些指标。
我们先来分析下**第一项指标FP**如果FP时间过久那么直接说明了一个问题那就是页面的HTML文件可能由于网络原因导致加载时间过久这块具体的分析过程你可以参考《[21Chrome开发者工具利用网络面板做性能分析](https://time.geekbang.org/column/article/138844)》这节内容。
**第二项是FMP**上面也提到过由于FMP计算复杂所以现在不建议使用该指标了另外由于LCP的计算规则简单所以推荐使用LCP指标具体文章你可以参考[这里](https://web.dev/lcp/)。不过是FMP还是LCP优化它们的方式都是类似的你可以结合上图如果FMP和LCP消耗时间过久那么有可能是加载关键资源花的时间过久也有可能是JavaScript执行过程中所花的时间过久所以我们可以针对具体的情况来具体分析。
**第三项是首屏时间(Speed Index)这就是我们上面提到的LCP**它表示填满首屏页面所消耗的时间首屏时间的值越大那么加载速度越慢具体的优化方式同优化第二项FMP是一样。
**第四项是首次CPU空闲时间(First CPU Idle)也称为First Interactive**它表示页面达到最小化可交互的时间也就是说并不需要等到页面上的所有元素都可交互只要可以对大部分用户输入做出响应即可。要缩短首次CPU空闲时长我们就需要尽可能快地加载完关键资源尽可能快地渲染出来首屏内容因此优化方式和第二项FMP和第三项LCP是一样的。
**第五项是完全可交互时间(Time to Interactive)简称TTI**它表示页面中所有元素都达到了可交互的时长。简单理解就这时候页面的内容已经完全显示出来了所有的JavaScript事件已经注册完成页面能够对用户的交互做出快速响应通常满足响应速度在50毫秒以内。如果要解决TTI时间过久的问题我们可以推迟执行一些和生成页面无关的JavaScript工作。
**第六项是最大估计输入延时(Max Potential First Input Delay**这个指标是估计你的Web页面在加载最繁忙的阶段 窗口中响应用户输入所需的时间为了改善该指标我们可以使用WebWorker来执行一些计算从而释放主线程。另一个有用的措施是重构CSS选择器以确保它们执行较少的计算。
## 总结
好了,今天的内容就介绍到这里,下面我来总结下本文的主要内容:
本文我们主要讨论如何优化加载阶段的Web应用的性能。
要想优化Web性能首先得需要有Web应用的性能数据。所以接下来我们介绍了Chrome采集Web性能数据的两个工具Performance和AuditsPerformance可以采集非常多的性能但是其使用难度大相反Audtis就简单了许多它会分析检测到的性能数据并给出站点的性能得分同时还会给我们提供一些优化建议。
我们先从简单的工具上手所以本文我们主要分析了Audits的使用方式先介绍了如何使用Audits生成性能报告然后我们解读了性能报告中的每一项内容。
大致了解Audits生成的性能报告之后我们又分析Web应用在加载阶段的几个关键时间点最后我们分析性能指标的具体含义以及如何提高性能指标的分数从而达到优化Web应用的目的。
通过介绍我们知道了Audits非常适合用来分析加载阶段的Web性能除此之外Audits还有其他非常实用的功能比如可以检测我们的代码是否符合一些最佳实践并给出提示这样我们就可以根据Audits的提示来决定是否需要优化我们的代码这个功能非常不错具体使用方式留给你自己去摸索了。
## 课后思考
在文中我们又分析Web应用在加载阶段的几个关键时间点在Audits中通过对这些时间点的分析输出了文中介绍的六项性能指标其实这些时间点也可以通过Performance的时间线(Timelines)来查看那么今天留给你的任务是提前熟悉下Performance工具并对照这文中加载阶段的几个时间点来熟悉下Performance的时间线(Timelines),欢迎在留言区分享你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给更多的朋友。

View File

@@ -0,0 +1,164 @@
<audio id="audio" title="加餐二任务调度有了setTimeOut为什么还要使用rAF" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/36/c0/36bb3a194534a23f6e9e5b3f7d0003c0.mp3"></audio>
你好,我是李兵。
我们都知道要想利用JavaScript实现高性能的动画那就得使用requestAnimationFrame这个API我们简称rAF那么为什么都推荐使用rAF而不是setTimeOut呢
要解释清楚这个问题就要从渲染进程的任务调度系统讲起理解了渲染进程任务调度系统你自然就明白了rAF和setTimeOut的区别。其次如果你理解任务调度系统那么你就能将渲染流水线和浏览器系统架构等知识串起来理解了这些概念也有助于你理解Performance标签是如何工作的。
要想了解最新Chrome的任务调度系统是怎么工作的我们得先来回顾下之前介绍的消息循环系统我们知道了渲染进程内部的大多数任务都是在主线程上执行的诸如JavaScript执行、DOM、CSS、计算布局、V8的垃圾回收等任务。要让这些任务能够在主线程上有条不紊地运行就需要引入消息队列。
在前面的《[16 | WebAPIsetTimeout是如何实现的](https://time.geekbang.org/column/article/134456)》这篇文章中,我们还介绍了,主线程维护了一个普通的消息队列和一个延迟消息队列,调度模块会按照规则依次取出这两个消息队列中的任务,并在主线程上执行。为了下文讲述方便,在这里我把普通的消息队列和延迟队列都当成一个消息队列。
新的任务都是被放进消息队列中去的,然后主线程再依次从消息队列中取出这些任务来顺序执行。这就是我们之前介绍的消息队列和事件循环系统。
## 单消息队列的队头阻塞问题
我们知道,渲染主线程会按照先进先出的顺序执行消息队列中的任务,具体地讲,当产生了新的任务,渲染进程会将其添加到消息队列尾部,在执行任务过程中,渲染进程会顺序地从消息队列头部取出任务并依次执行。
在最初,采用这种方式没有太大的问题,因为页面中的任务还不算太多,渲染主线程也不是太繁忙。不过浏览器是向前不停进化的,其进化路线体现在架构的调整、功能的增加以及更加精细的优化策略等方面,这些变化让渲染进程所需要处理的任务变多了,对应的渲染进程的主线程也变得越拥挤。下图所展示的仅仅是部分运行在主线程上的任务,你可以参考下:
<img src="https://static001.geekbang.org/resource/image/fa/f4/fa9f5853a5dcad650aaaf39072820ef4.png" alt="">
你可以试想一下在基于这种单消息队列的架构下如果用户发出一个点击事件或者缩放页面的事件而在此时该任务前面可能还有很多不太重要的任务在排队等待着被执行诸如V8的垃圾回收、DOM定时器等任务如果执行这些任务需要花费的时间过久的话那么就会让用户产生卡顿的感觉。你可以参看下图
<img src="https://static001.geekbang.org/resource/image/cc/ff/cc7c32fa82207cece9c78015e4b841ff.jpg" alt="">
因此,**在单消息队列架构下,存在着低优先级任务会阻塞高优先级任务的情况**比如在一些性能不高的手机上有时候滚动页面需要等待一秒以上。这像极了我们在介绍HTTP协议时所谈论的队头阻塞问题那么我们也把这个问题称为消息队列的队头阻塞问题吧。
## Chromium是如何解决队头阻塞问题的
为了解决由于单消息队列而造成的队头阻塞问题Chromium团队从2013年到现在花了大量的精力在持续重构底层消息机制。在接下来的篇幅里我会按照Chromium团队的重构消息系统的思路来带你分析下他们是如何解决掉队头阻塞问题的。
#### 1. 第一次迭代:引入一个高优先级队列
首先在最理想的情况下,我们希望能够快速跟踪高优先级任务,比如在交互阶段,下面几种任务都应该视为高优先级的任务:
- 通过鼠标触发的点击任务、滚动页面任务;
- 通过手势触发的页面缩放任务;
- 通过CSS、JavaScript等操作触发的动画特效等任务。
这些任务被触发后,用户想立即得到页面的反馈,所以我们需要让这些任务能够优先与其他的任务执行。要实现这种效果,我们可以增加一个高优级的消息队列,将高优先级的任务都添加到这个队列里面,然后优先执行该消息队列中的任务。最终效果如下图所示:
<img src="https://static001.geekbang.org/resource/image/03/c1/039fdf4c399d20a75d7dea9448cc8fc1.jpg" alt="">
观察上图,我们使用了一个优先级高的消息队列和一个优先级低消息队列,渲染进程会将它认为是紧急的任务添加到高优先级队列中,不紧急的任务就添加到低优先级的队列中。然后我们再在渲染进程中引入一个**任务调度器**,负责从多个消息队列中选出合适的任务,通常实现的逻辑,先按照顺序从高优先级队列中取出任务,如果高优先级的队列为空,那么再按照顺序从低优级队列中取出任务。
我们还可以更进一步,将任务划分为多个不同的优先级,来实现更加细粒度的任务调度,比如可以划分为高优先级,普通优先级和低优先级,最终效果如下图所示:
<img src="https://static001.geekbang.org/resource/image/d7/78/d7c71113c6c13047fb79e7d120173b78.jpg" alt="">
观察上图,我们实现了三个不同优先级的消息队列,然后可以使用任务调度器来统一调度这三个不同消息队列中的任务。
好了,现在我们引入了多个消息队列,结合任务调度器我们就可以灵活地调度任务了,这样我们就可以让高优先级的任务提前执行,采用这种方式似乎解决了消息队列的队头阻塞问题。
不过大多数任务需要保持其相对执行顺序,如果将用户输入的消息或者合成消息添加进多个不同优先级的队列中,那么这种任务的相对执行顺序就会被打乱,甚至有可能出现还未处理输入事件,就合成了该事件要显示的图片。因此我们需要让一些相同类型的任务保持其相对执行顺序。
#### 2. 第二次迭代:根据消息类型来实现消息队列
要解决上述问题,我们可以为不同类型的任务创建不同优先级的消息队列,比如:
- 可以创建输入事件的消息队列,用来存放输入事件。
- 可以创建合成任务的消息队列,用来存放合成事件。
- 可以创建默认消息队列,用来保存如资源加载的事件和定时器回调等事件。
- 还可以创建一个空闲消息队列用来存放V8的垃圾自动垃圾回收这一类实时性不高的事件。
最终实现效果如下图所示:
<img src="https://static001.geekbang.org/resource/image/56/ce/56ec510f7f7d4738e9db83dbd51f3fce.png" alt="">
通过迭代,这种策略已经相当实用了,但是它依然存在着问题,那就是这几种消息队列的优先级都是固定的,任务调度器会按照这种固定好的静态的优先级来分别调度任务。那么静态优先级会带来什么问题呢?
我们在《[25 | 页面性能:如何系统地优化页面?](https://time.geekbang.org/column/article/143889)》这节分析过页面的生存周期,页面大致的生存周期大体分为两个阶段,加载阶段和交互阶段。
虽然在交互阶段采用上述这种静态优先级的策略没有什么太大问题的但是在页面加载阶段如果依然要优先执行用户输入事件和合成事件那么页面的解析速度将会被拖慢。Chromium团队曾测试过这种情况使用静态优先级策略网页的加载速度会被拖慢14%。
## 3. 第三次迭代:动态调度策略
可以看出,我们所采用的优化策略像个跷跷板,虽然优化了高优先级任务,却拖慢低优先级任务,之所以会这样,是因为我们采取了静态的任务调度策略,对于各种不同的场景,这种静态策略就显得过于死板。
所以我们还得根据实际场景来继续平衡这个跷跷板,也就是说在不同的场景下,根据实际情况,动态调整消息队列的优先级。一图胜过千言,我们先看下图:
<img src="https://static001.geekbang.org/resource/image/3c/f5/3cc95247daae7f90f0dced017d349af5.png" alt="">
这张图展示了Chromium在不同的场景下是如何调整消息队列优先级的。通过这种动态调度策略就可以满足不同场景的核心诉求了同时这也是Chromium当前所采用的任务调度策略。
上图列出了三个不同的场景分别是加载过程合成过程以及正常状态。下面我们就结合这三种场景来分析下Chromium为何做这种调整。
首先我们来看看**页面加载阶段**的场景在这个阶段用户的最高诉求是在尽可能短的时间内看到页面至于交互和合成并不是这个阶段的核心诉求因此我们需要调整策略在加载阶段将页面解析JavaScript脚本执行等任务调整为优先级最高的队列降低交互合成这些队列的优先级。
页面加载完成之后就进入了**交互阶段**在介绍Chromium是如何调整交互阶段的任务调度策略之前我们还需要岔开一下来回顾下页面的渲染过程。
在《[06 | 渲染流程HTML、CSS和JavaScript是如何变成页面的](https://time.geekbang.org/column/article/118826)》和《[24 | 分层和合成机制为什么CSS动画比JavaScript高效](https://time.geekbang.org/column/article/141842)》这两节,我们分析了一个页面是如何渲染并显示出来的。
在显卡中有一块叫着**前缓冲区**的地方这里存放着显示器要显示的图像显示器会按照一定的频率来读取这块前缓冲区并将前缓冲区中的图像显示在显示器上不同的显示器读取的频率是不同的通常情况下是60HZ也就是说显示器会每间隔1/60秒就读取一次前缓冲区。
如果浏览器要更新显示的图片,那么浏览器会将新生成的图片提交到显卡的**后缓冲区**中提交完成之后GPU会将**后缓冲区和前缓冲区互换位置**也就是前缓冲区变成了后缓冲区后缓冲区变成了前缓冲区这就保证了显示器下次能读取到GPU中最新的图片。
这时候我们会发现,显示器从前缓冲区读取图片,和浏览器生成新的图像到后缓冲区的过程是不同步的,如下图所示:
<img src="https://static001.geekbang.org/resource/image/1c/38/1c3a9d8a0f56b73331041ea603ad3738.png" alt="">
这种显示器读取图片和浏览器生成图片不同步,容易造成众多问题。
- 如果渲染进程生成的帧速比屏幕的刷新率慢,那么屏幕会在两帧中显示同一个画面,当这种断断续续的情况持续发生时,用户将会很明显地察觉到动画卡住了。
- 如果渲染进程生成的帧速率实际上比屏幕刷新率快那么也会出现一些视觉上的问题比如当帧速率在100fps而刷新率只有60Hz的时候GPU所渲染的图像并非全都被显示出来这就会造成丢帧现象。
- 就算屏幕的刷新频率和GPU更新图片的频率一样由于它们是两个不同的系统所以屏幕生成帧的周期和VSync的周期也是很难同步起来的。
所以VSync和系统的时钟不同步就会造成掉帧、卡顿、不连贯等问题。
为了解决这些问题就需要将显示器的时钟同步周期和浏览器生成页面的周期绑定起来Chromium也是这样实现那么下面我们就来看看Chromium具体是怎么实现的
**当显示器将一帧画面绘制完成后并在准备读取下一帧之前显示器会发出一个垂直同步信号vertical synchronization给GPU简称 VSync。**这时候浏览器就会充分利用好VSync信号。
具体地讲当GPU接收到VSync信号后会将VSync信号同步给浏览器进程浏览器进程再将其同步到对应的渲染进程渲染进程接收到VSync信号之后就可以准备绘制新的一帧了具体流程你可以参考下图
<img src="https://static001.geekbang.org/resource/image/06/08/06206ed4846e9531351a0cb7d1db6208.png" alt="">
上面其实是非常粗略的介绍,实际实现过程也是非常复杂的,如果感兴趣,你可以参考[这篇文章](https://docs.google.com/document/d/16822du6DLKDZ1vQVNWI3gDVYoSqCSezgEmWZ0arvkP8/edit)。
好了我们花了很大篇幅介绍了VSync和页面中的一帧是怎么显示出来有了这些知识我们就可以回到主线了来分析下渲染进程是如何优化交互阶段页面的任务调度策略的
从上图可以看出,当渲染进程接收到用户交互的任务后,接下来大概率是要进行绘制合成操作,因此我们可以设置,**当在执行用户交互的任务时,将合成任务的优先级调整到最高。**
接下来处理完成DOM计算好布局和绘制就需要将信息提交给合成线程来合成最终图片了然后合成线程进入工作状态。现在的场景是合成线程在工作了**那么我们就可以把下个合成任务的优先级调整为最低,并将页面解析、定时器等任务优先级提升。**
在合成完成之后合成线程会提交给渲染主线程提交完成合成的消息如果当前合成操作执行的非常快比如从用户发出消息到完成合成操作只花了8毫秒因为VSync同步周期是16.661/60毫秒那么这个VSync时钟周期内就不需要再次生成新的页面了。那么从合成结束到下个VSync周期内就进入了一个空闲时间阶段那么就可以在这段空闲时间内执行一些不那么紧急的任务比如V8的垃圾回收或者通过window.requestIdleCallback()设置的回调任务等,都会在这段空闲时间内执行。
#### 4. 第四次迭代:任务饿死
好了,以上方案看上去似乎非常完美了,不过依然存在一个问题,那就是在某个状态下,一直有新的高优先级的任务加入到队列中,这样就会导致其他低优先级的任务得不到执行,这称为任务饿死。
Chromium为了解决任务饿死的问题给每个队列设置了执行权重也就是如果连续执行了一定个数的高优先级的任务那么中间会执行一次低优先级的任务这样就缓解了任务饿死的情况。
## 总结
好了,本节的内容就介绍到这里,下面我来总结下本文的主要内容:
首先我们分析了基于单消息队列会引起队头阻塞的问题,为了解决队头阻塞问题,我们引入了多个不同优级的消息队列,并将紧急的任务添加到高优先级队列,不过大多数任务需要保持其相对执行顺序,如果将用户输入的消息或者合成消息添加进多个不同优先级的队列中,那么这种任务的相对执行顺序就会被打乱,所以我们又迭代了第二个版本。
在第二个版本中,按照不同的任务类型来划分任务优先级,不过由于采用的静态优先级策略,对于其他一些场景,这种静态调度的策略并不是太适合,所以接下来,我们又迭代了第三版。
第三个版本基于不同的场景来动态调整消息队列的优先级到了这里已经非常完美了不过依然存在着任务饿死的问题为了解决任务饿死的问题我们给每个队列一个权重如果连续执行了一定个数的高优先级的任务那么中间会执行一次低优先级的任务这样我们就完成了Chromium的任务改造。
通过整个过程的分析,我们应该能理解,在开发一个项目时,不要试图去找最完美的方案,完美的方案往往是不存在的,我们需要根据实际的场景来寻找最适合我们的方案。
## 思考题
我们知道CSS动画是由渲染进程自动处理的所以渲染进程会让CSS渲染每帧动画的过程与VSync的时钟保持一致,这样就能保证CSS动画的高效率执行。
但是JavaScript是由用户控制的如果采用setTimeout来触发动画每帧的绘制那么其绘制时机是很难和VSync时钟保持一致的所以JavaScript中又引入了window.requestAnimationFrame用来和VSync的时钟周期同步那么我留给你的问题是你知道requestAnimationFrame回调函数的执行时机吗
## 参考资料
下面是我参考的一些资料:
- [Blink Scheduler ](https://chromium.googlesource.com/chromium/src/+/refs/tags/80.0.3968.1/third_party/blink/renderer/platform/scheduler/)
- [Blink Scheduler PPT](https://docs.google.com/presentation/d/1V09Qq08_jOucvOFs-C7P4Hz2Vsswa6imqLxAf7ONomQ/edit#slide=id.g3ef47b745_0104)
- [Chrome的消息类型](https://chromium.googlesource.com/chromium/src/third_party/+/master/blink/public/platform/task_type.h)
- [Chrome消息优先级](https://chromium.googlesource.com/chromium/src/base/+/refs/heads/master/task/sequence_manager/task_queue.h)
- [无头浏览器](https://docs.google.com/presentation/d/1OnvR0S2s8yrn0KWAJaFEgOasrSnwR_I7JFzTB6f-G3U/htmlpresent)
欢迎在留言区分享你的想法。感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给更多的朋友。

View File

@@ -0,0 +1,242 @@
<audio id="audio" title="加餐五 | 性能分析工具如何分析Performance中的Main指标" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/40/cf/40fc10636875760d547fdc12418b9ecf.mp3"></audio>
你好,我是李兵
上节我们介绍了如何使用Performance而且我们还提到了性能指标面板中的Main指标它详细地记录了渲染主线程上的任务执行记录通过分析Main指标我们就能够定位到页面中所存在的性能问题本节我们就来介绍如何分析Main指标。
## 任务 vs 过程
不过在开始之前我们要讲清楚两个概念那就是Main指标中的任务和过程在《[15 | 消息队列和事件循环:页面是怎么活起来的?](https://time.geekbang.org/column/article/132931)》和《[加餐二任务调度有了setTimeOut为什么还要使用rAF](https://time.geekbang.org/column/article/169468)》这两节我们分析过渲染进程中维护了消息队列如果通过SetTimeout设置的回调函数通过鼠标点击的消息事件都会以任务的形式添加消息队列中然后任务调度器会按照一定规则从消息队列中取出合适的任务并让其在渲染主线程上执行。
而我们今天所分析的Main指标就记录渲染主线上所执行的全部**任务**,以及每个任务的详细执行**过程**。
你可以打开Chrome的开发者工具选择Performance标签然后录制加载阶段任务执行记录然后关注Main指标如下图所示
<img src="https://static001.geekbang.org/resource/image/c3/cc/c3add6d821fd2a45a14bb2388c9c2dcc.png" alt="">
观察上图,图上方有很多一段一段灰色横条,**每个灰色横条就对应了一个任务,灰色长条的长度对应了任务的执行时长**。通常,渲染主线程上的任务都是比较复杂的,如果只单纯记录任务执行的时长,那么依然很难定位问题,因此,还需要将任务执行过程中的一些关键的细节记录下来,这些细节就是任务的**过程**,灰线下面的横条就是一个个过程,同样这些横条的长度就代表这些过程执行的时长。
直观地理解你可以把任务看成是一个Task函数在执行Task函数的过程中它会调用一系列的子函数这些子函数就是我们所提到的**过程**。为了让你更好地理解,我们来分析下面这个任务的图形:
<img src="https://static001.geekbang.org/resource/image/aa/18/aabfd0e5e746bbaeaf14c62c703a7718.png" alt="">
观察上面这个任务记录的图形你可以把该图形看成是下面Task函数的执行过程
```
function A(){
A1()
A2()
}
function Task(){
A()
B()
}
Task()
```
结合代码和上面的图形,我们可以得出以下信息:
- Task任务会首先调用A过程
- 随后A过程又依次调用了A1和A2过程然后A过程执行完毕
- 随后Task任务又执行了B过程
- B过程执行结束Task任务执行完成
- 从图中可以看出A过程执行时间最长所以在A1过程时拉长了整个任务的执行时长。
## 分析页面加载过程
通过以上介绍相信你已经掌握了如何解读Main指标中的任务了那么接下来我们就可以结合Main指标来分析页面的加载过程。我们先来分析一个简单的页面代码如下所示
```
&lt;html&gt;
&lt;head&gt;
&lt;title&gt;Main&lt;/title&gt;
&lt;style&gt;
area {
border: 2px ridge;
}
box {
background-color: rgba(106, 24, 238, 0.26);
height: 5em;
margin: 1em;
width: 5em;
}
&lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;div class=&quot;area&quot;&gt;
&lt;div class=&quot;box rAF&quot;&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;script&gt;
function setNewArea() {
let el = document.createElement('div')
el.setAttribute('class', 'area')
el.innerHTML = '&lt;div class=&quot;box rAF&quot;&gt;&lt;/div&gt;'
document.body.append(el)
}
setNewArea()
&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
```
观察这段代码我们可以看出它只是包含了一段CSS样式和一段JavaScript内嵌代码其中在JavaScript中还执行了DOM操作了我们就结合这段代码来分析页面的加载流程。
首先生成报告页再观察报告页中的Main指标由于阅读实际指标比较费劲所以我手动绘制了一些关键的任务和其执行过程如下图所示
<img src="https://static001.geekbang.org/resource/image/51/4b/5175c0405fa4d9d1a1e4fd261b92dc4b.png" alt="">
通过上面的图形我们可以看出,加载过程主要分为三个阶段,它们分别是:
1. 导航阶段该阶段主要是从网络进程接收HTML响应头和HTML响应体。
1. 解析HTML数据阶段该阶段主要是将接收到的HTML数据转换为DOM和CSSOM。
1. 生成可显示的位图阶段该阶段主要是利用DOM和CSSOM经过计算布局、生成层树(LayerTree)、生成绘制列表(Paint)、完成合成等操作,生成最终的图片。
那么接下来我就按照这三个步骤来介绍如何解读Main指标上的数据。
#### 导航阶段
我们先来看**导航阶段**,不过在分析这个阶段之前,我们简要地回顾下导航流程,大致的流程是这样的:
当你点击了Performance上的重新录制按钮之后浏览器进程会通知网络进程去请求对应的URL资源一旦网络进程从服务器接收到URL的响应头便立即判断该响应头中的content-type字段是否属于text/html类型如果是那么浏览器进程会让当前的页面执行退出前的清理操作比如执行JavaScript中的beforunload事件清理操作执行结束之后就准备显示新页面了这包括了解析、布局、合成、显示等一系列操作。
因此,在导航阶段,这些任务实际上是在老页面的渲染主线程上执行的。如果你想要了解导航流程的详细细节,我建议你回顾下《[04 | 导航流程从输入URL到页面展示这中间发生了什么](https://time.geekbang.org/column/article/117637)》这篇文章,在这篇文中我们有介绍导航流程,而导航阶段和导航流程又有着密切的关联。
回顾了导航流程之后,我们接着来分析第一个阶段的任务图形,为了让你更加清晰观察上图中的导航阶段,我将其放大了,最终效果如下图所示:
<img src="https://static001.geekbang.org/resource/image/39/f3/39c8e28df9e60f2e0d8378da350dc6f3.png" alt="">
观察上图,如果你熟悉了导航流程,那么就很容易根据图形分析出这些任务的执行流程了。
具体地讲,当你点击重新加载按钮后,当前的页面会执行上图中的这个任务:
- 该任务的第一个子过程就是Send request该过程表示网络请求已被发送。然后该任务进入了等待状态。
- 接着由网络进程负责下载资源当接收到响应头的时候该任务便执行Receive Respone过程该过程表示接收到HTTP的响应头了。
- 接着执行DOM事件pagehide、visibilitychange和unload等事件如果你注册了这些事件的回调函数那么这些回调函数会依次在该任务中被调用。
- 这些事件被处理完成之后那么接下来就接收HTML数据了这体现在了Recive Data过程Recive Data过程表示请求的数据已被接收如果HTML数据过多会存在多个 Receive Data 过程。
等到所有的数据都接收完成之后渲染进程会触发另外一个任务该任务主要执行Finish load过程该过程表示网络请求已经完成。
#### 解析HTML数据阶段
好了,导航阶段结束之后,就进入到了**解析HTML数据阶段**了这个阶段的主要任务就是通过解析HTML数据、解析CSS数据、执行JavaScript来生成DOM和CSSOM。那么下面我们继续来分析这个阶段的图形看看它到底是怎么执行的同样我也放大了这个阶段的图形你可以观看下图
<img src="https://static001.geekbang.org/resource/image/89/9d/89f2f61ed51d7a543390c4262489479d.png" alt="">
观察上图这个图形我们可以看出其中一个主要的过程是HTMLParser顾名思义这个过程是用来解析HTML文件解析的就是上个阶段接收到的HTML数据。
1. 在ParserHTML的过程中如果解析到了script标签那么便进入了脚本执行过程也就是图中的Evalute Script。
1. 我们知道要执行一段脚本我们需要首先编译该脚本于是在Evalute Script过程中先进入了脚本编译过程也就是图中的Complie Script。脚本编译好之后就进入程序执行过程执行全局代码时V8会先构造一个anonymous过程在执行anonymous过程中会调用setNewArea过程setNewArea过程中又调用了createElement由于之后调用了document.append方法该方法会触发DOM内容的修改所以又强制执行了ParserHTML过程生成的新的DOM。
1. DOM生成完成之后会触发相关的DOM事件比如典型的DOMContentLoaded还有readyStateChanged。
DOM生成之后ParserHTML过程继续计算样式表也就是Reculate Style这就是生成CSSOM的过程关于Reculate Style过程你可以参考我们在《[05 | 渲染流程HTML、CSS和JavaScript是如何变成页面的](https://time.geekbang.org/column/article/118205)》节的内容到了这里一个完整的ParserHTML任务就执行结束了。
#### 生成可显示位图阶段
生成了DOM和CSSOM之后就进入了第三个阶段生成页面上的位图。通常这需要经历**布局(Layout)、分层、绘制、合成**等一系列操作,同样,我将第三个阶段的流程也放大了,如下图所示:
<img src="https://static001.geekbang.org/resource/image/2b/ce/2bfdcdbf340b0ee7ce5d8a6109a56bce.png" alt="">
结合上图我们可以发现在生成完了DOM和CSSOM之后渲染主线程首先执行了一些DOM事件诸如readyStateChange、load、pageshow。具体地讲如果你使用JavaScript监听了这些事件那么这些监听的函数会被渲染主线程依次调用。
接下来就正式进入显示流程了,大致过程如下所示。
1. 首先执行布局,这个过程对应图中的**Layout**。
1. 然后更新层树(LayerTree),这个过程对应图中的**Update LayerTree。**
1. 有了层树之后,就需要为层树中的每一层准备绘制列表了,这个过程就称为**Paint。**
1. 准备每层的绘制列表之后,就需要利用绘制列表来生成相应图层的位图了,这个过程对应图中的**Composite Layers**。
走到了Composite Layers这步主线程的任务就完成了接下来主线程会将合成的任务完全教给合成线程来执行下面是具体的过程你也可以对照着**Composite、Raster和GPU**这三个指标来分析,参考下图:
<img src="https://static001.geekbang.org/resource/image/e6/12/e60c8c65dd3d364f73c19d4b0475d112.png" alt="">
结合渲染流水线和上图,我们再来梳理下最终图像是怎么显示出来的。
1. 首先主线程执行到Composite Layers过程之后便会将绘制列表等信息提交给合成线程合成线程的执行记录你可以通过**Compositor指标**来查看。
1. 合成线程维护了一个**Raster**线程池,线程池中的每个线程称为**Rasterize**,用来执行光栅化操作,对应的任务就是**Rasterize Paint**。
1. 当然光栅化操作并不是在**Rasterize线程**中直接执行的而是在GPU进程中执行的因此Rasterize线程需要和GPU线程保持通信。
1. 然后GPU生成图像最终这些图层会被提交给浏览器进程浏览器进程将其合成并最终显示在页面上。
#### 通用分析流程
通过对Main指标的分析我们把导航流程解析流程和最终的显示流程都串起来了通过Main指标的分析我们对页面的加载过程执行流程又有了新的认识虽然实际情况比这个复杂但是万变不离其宗所有的流程都是围绕这条线来展开的也就是说先经历导航阶段然后经历HTML解析最后生成最终的页面。
## 总结
本文主要的目的是让我们学会如何分析Main指标。通过页面加载过程的分析就能掌握一套标准的分析Main指标的方法在该方法中我将加载过程划分为三个阶段
1. 导航阶段;
1. 解析HTML文件阶段
1. 生成位图阶段。
在导航流程中主要是处理响应头的数据并执行一些老页面退出之前的清理操作。在解析HTML数据阶段主要是解析HTML数据、解析CSS数据、执行JavaScript来生成DOM和CSSOM。最后在生成最终显示位图的阶段主要是将生成的DOM和CSSOM合并这包括了布局(Layout)、分层、绘制、合成等一系列操作。
通过Main指标我们完整地分析了一个页面从加载到显示的过程了解这个流程我们自然就会去分析页面的性能瓶颈比如你可以通过Main指标来分析JavaScript是否执行时间过久或者通过Main指标分析代码里面是否存在强制同步布局等操作分析出来这些原因之后我们可以有针对性地去优化我们的程序。
## 思考题
在《[18](https://time.geekbang.org/column/article/135624)[|](https://time.geekbang.org/column/article/135624) [宏任务和微任务](https://time.geekbang.org/column/article/135624)[:不是所有任务都是一个待遇](https://time.geekbang.org/column/article/135624)》这节中介绍微任务时,我们提到过,在一个任务的执行过程中,会在一些特定的时间点来检查是否有微任务需要执行,我们把这些特定的检查时间点称为**检查点。**了解了检查点之后你可以通过Performance的Main指标来分析下面这两段代码
```
&lt;body&gt;
&lt;script&gt;
let p = new Promise(function (resolve, reject) {
resolve(&quot;成功!&quot;);
});
p.then(function (successMessage) {
console.log(&quot;p! &quot; + successMessage);
})
let p1 = new Promise(function (resolve, reject) {
resolve(&quot;成功!&quot;);
});
p1.then(function (successMessage) {
console.log(&quot;p1! &quot; + successMessage);
})
&lt;/script&gt;
&lt;/bod&gt;
```
```
&lt;body&gt;
&lt;script&gt;
let p = new Promise(function (resolve, reject) {
resolve(&quot;成功!&quot;);
});
p.then(function (successMessage) {
console.log(&quot;p! &quot; + successMessage);
})
&lt;/script&gt;
&lt;script&gt;
let p1 = new Promise(function (resolve, reject) {
resolve(&quot;成功!&quot;);
});
p1.then(function (successMessage) {
console.log(&quot;p1! &quot; + successMessage);
})
&lt;/script&gt;
&lt;/body&gt;
```
今天留给你的任务是结合Main指标来分析上面这两段代码中微任务执行的时间点有何不同并给出分析结果和原因。欢迎在留言区与我交流。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给更多的朋友。

View File

@@ -0,0 +1,129 @@
<audio id="audio" title="加餐六HTTPS浏览器如何验证数字证书" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/50/64/50aa22ae9f97ffa429e36f8993fef064.mp3"></audio>
你好,我是李兵。
在《[36HTTPS让数据传输更安全](https://time.geekbang.org/column/article/156181)》这篇文章中,我们聊了下面几个问题:
- HTTPS使用了对称和非对称的混合加密方式这解决了数据传输安全的问题
- HTTPS引入了中间机构CACA通过给服务器颁发数字证书解决了浏览器对服务器的信任问题
- 服务器向CA机构申请证书的流程
- 浏览器验证服务器数字证书的流程。
不过由于篇幅限制,关于“**浏览器如何验证数字证书”**的这个问题我们并没有展开介绍。那么今天我们就继续聊一聊这个问题。了解了这个问题可以方便我们把完整的HTTPS流程给串起来无论对于我们理解HTTPS的底层技术还是理解业务都是非常有帮助的。
因为本文是第36讲的延伸所以在分析之前我们还是有必要回顾下**数字证书申请流程**和**浏览器验证证书的流程**同时你最好也能回顾下第36讲。
## 数字证书申请流程
我们先来回顾下数字证书的申请流程比如极客时间向一个CA机构申请数字证书流程是什么样的呢
首先极客时间填写了一张含有**自己身份信息**的表单身份信息包括了自己公钥、站点资料、公司资料等信息然后将其提交给了CA机构CA机构会审核表单中内容的真实性审核通过后CA机构会拿出自己的私钥对表单的内容进行一连串操作包括了对明文资料进行Hash计算得出信息摘要 利用CA的私钥加密信息摘要得出数字签名最后将数字签名也写在表单上并将其返还给极客时间这样就完成了一次数字证书的申请操作。
大致流程你也可以参考下图:
<img src="https://static001.geekbang.org/resource/image/f5/a6/f569c80f8f4b25b3bf384037813cdca6.png" alt="">
## 浏览器验证证书的流程
现在极客时间的官网有了CA机构签发的数字证书那么接下来就可以将数字证书应用在HTTPS中了。
我们知道在浏览器和服务器建立HTTPS链接的过程中浏览器首先会向服务器请求数字证书之后浏览器要做的第一件事就是验证数字证书。那么这里所说的“验证”它到底是在验证什么呢
具体地讲,浏览器需要验证**证书的有效期**、**证书是否被CA吊销**、**证书是否<strong><strong>是**</strong>合法的CA机构颁发的。</strong>
数字证书和身份证一样也是有时间期限的,所以**第一部分就是验证证书的有效期**,这部分比较简单,因为证书里面就含有证书的有效期,所以浏览器只需要判断当前时间是否在证书的有效期范围内即可。
有时候有些数字证书被CA吊销了吊销之后的证书是无法使用的所以**第二部分就是验证数字证书是否被吊销了**。通常有两种方式,一种是下载吊销证书列表-CRL (Certificate Revocation Lists),第二种是在线验证方式-OCSP (Online Certificate Status Protocol) ,它们各有优缺点,在这里我就不展开介绍了。
最后,还要**验证极客时间的数字证书是否是CA机构颁发****的,**验证的流程非常简单:
- 首先,浏览器利用证书的原始信息计算出信息摘要;
- 然后,利用**CA的公钥**来解密数字证书中的**数字签名**,解密出来的数据也是信息摘要;
- 最后,判断这两个信息摘要是否相等就可以了。
<img src="https://static001.geekbang.org/resource/image/ae/08/ae7dbe9f8785441721deb1f7b316f708.png" alt="">
通过这种方式就验证了数字证书是否是由CA机构所签发的不过这种方式又带来了一个新的疑问**浏览器是怎么获取到CA公钥的**
## 浏览器是怎么获取到CA公钥的
通常当你部署HTTP服务器的时候除了部署当前的数字证书之外还需要部署CA机构的数字证书CA机构的数字证书包括了CA的公钥以及CA机构的一些基础信息。
因此,极客时间服务器就有了两个数字证书:
- 给极客时间域名的数字证书;
- 给极客时间签名的CA机构的数字证书。
然后在建立HTTPS链接时服务器会将这两个证书一同发送给浏览器于是浏览器就可以获取到CA的公钥了。
如果有些服务器没有部署CA的数字证书那么浏览器还可以通过网络去下载CA证书不过这种方式多了一次证书下载操作会拖慢首次打开页面的请求速度一般不推荐使用。
现在浏览器端就有了极客时间的证书和CA的证书完整的验证流程就如下图所示
<img src="https://static001.geekbang.org/resource/image/cb/d3/cb150e316f4847c71288a8df50bfebd3.png" alt="">
我们有了CA的数字证书也就可以获取得CA的公钥来验证极客时间数字证书的可靠性了。
解决了获取CA公钥的问题新的问题又来了如果这个证书是一个恶意的CA机构颁发的怎么办所以我们还需要**浏览器证明这个CA机构是个合法的机构。**
## 证明CA机构的合法性
这里并没有一个非常好的方法来证明CA的合法性妥协的方案是直接在操作系统中内置这些CA机构的数字证书如下图所示
<img src="https://static001.geekbang.org/resource/image/43/0b/43a732eb2ba47d06fbef20c515bd990b.png" alt="">
我们将所有CA机构的数字证书都内置在操作系统中这样当需要使用某CA机构的公钥时我们只需要依据CA机构名称就能查询到对应的数字证书了然后再从数字证书中取出公钥。
可以看到,这里有一个假设条件,浏览器默认信任操作系统内置的证书为合法证书,虽然这种方式不完美,但是却是最实用的一个。
不过这种方式依然存在问题,因为在实际情况下,**CA机构众多因此操作系统不可能将每家CA的数字证书都内置进操作系统**。
## 数字证书链
于是人们又想出来一个折中的方案,将颁发证书的机构划分为两种类型,**根CA(Root CAs)<strong>和**中间CA(Intermediates CAs)</strong>通常申请者都是向中间CA去申请证书的而根CA作用就是给中间CA做认证一个根CA会认证很多中间的CA而这些中间CA又可以去认证其他的中间CA。
因此每个根CA机构都维护了一个树状结构一个根CA下面包含多个中间CA而中间CA又可以包含多个中间CA。这样就形成了一个证书链你可以沿着证书链从用户证书追溯到根证书。
比如你可以在Chrome上打开极客时间的官网然后点击地址栏前面的那把小锁你就可以看到*.geekbang.org的证书是由中间CA GeoTrust RSA CA2018颁发的而中间CA GeoTrust RSA CA2018又是由根CA DigiCert Global Root CA颁发的所以这个证书链就是*.geekbang.org—&gt;GeoTrust RSA CA2018&gt;DigiCert Global Root CA。你可以参看下图
<img src="https://static001.geekbang.org/resource/image/10/b7/10616d8fc323d33bdecb09b503551cb7.png" alt="">
因此浏览器验证极客时间的证书时,会先验证*.geekbang.org的证书如果合法再验证中间CA的证书如果中间CA也是合法的那么浏览器会继续验证这个中间CA的根证书。
到了这里,依然存在一个问题,那就是**浏览器怎么证明根证书是合法的?**
## 如何验证根证书的合法性
其实浏览器的判断策略很简单,它只是简单地判断这个根证书在不在操作系统里面,如果在,那么浏览器就认为这个根证书是合法的,如果不在,那么就是非法的。
如果某个机构想要成为根CA并让它的根证书内置到操作系统中那么这个机构首先要通过WebTrust国际安全审计认证。
什么是WebTrust认证
WebTrust是由两大著名注册会计师协会AICPA美国注册会计师协会和CICA加拿大注册会计师协会共同制定的安全审计标准主要对互联网服务商的系统及业务运作逻辑安全性、保密性等共计七项内容进行近乎严苛的审查和鉴证。 只有通过WebTrust国际安全审计认证根证书才能预装到主流的操作系统并成为一个可信的认证机构。
目前通过WebTrust认证的根CA有 Comodo、geotrust、rapidssl、symantec、thawte、digicert等。也就是说这些根CA机构的根证书都内置在个大操作系统中只要能从数字证书链往上追溯到这几个根证书浏览器就会认为使用者的证书是合法的。
## 总结
好了,今天的内容就介绍到这里,下面我们总结下本文的主要内容:
我们先回顾了数字证书的申请流程,接着我们重点介绍了浏览器是如何验证数字证书的。
首先浏览器需要CA的数字证书才能验证极客时间的数字证书接下来我们需要验证CA证书的合法性最简单的方法是将CA证书内置在操作系统中。
不过CA机构非常多内置每家的证书到操作系统中是不现实的于是我们采用了一个折中的策略将颁发证书的机构划分为两种类型**根CA(Root CAs)<strong>和**中间CA(Intermediates CAs)</strong>通常申请者都是向中间CA去申请证书的而根CA作用就是给中间CA做认证一个根CA会认证很多中间的CA而这些中间CA又可以去认证其他的中间CA。
于是又引出了数字证书链浏览器先利用中间CA的数字证书来验证用户证书再利用根证书来验证中间CA证书的合法性最后浏览器会默认相信内置在系统中的根证书。不过要想在操作系统内部内置根证书却并不容易这需要通过WebTrust认证这个认证审核非常严格。
通过分析这个流程可以发现,浏览器默认信任操作系统内置的根证书,这也会带来一个问题,如果黑客入侵了你的电脑,那么黑客就有可能往你系统中添加恶意根数字证书,那么当你访问黑客站点的时候,浏览器甚至有可能会提示该站点是安全的。
因此HTTPS并非是绝对安全的采用HTTPS只是加固了城墙的厚度但是城墙依然有可能被突破。
## 课后思考
今天留给你的任务是复述下浏览器是怎么验证数字证书的,如果中间卡住了,欢迎在留言区提问交流。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给更多的朋友。

View File

@@ -0,0 +1,144 @@
<audio id="audio" title="加餐四页面性能工具如何使用Performance" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/80/df/80439d418173ccc067e6a7e567928fdf.mp3"></audio>
你好,我是李兵。
在分析页面性能时如果说Audits是道开胃菜那么Performance才是正餐之所这样说主要是因为Performance可以记录站点在运行过程中的性能数据有了这些性能数据我们就可以回放整个页面的执行过程这样就方便我们来定位和诊断每个时间段内页面的运行情况从而有效帮助我们找出页面的性能瓶颈。
不同于AuditsPerofrmance不会给出性能得分也不会给出优化建议它只是单纯地采集性能数据并将采集到的数据按照时间线的方式来展现我们要做的就是依据原始数据来分析Web应用的性能问题。
那么本节我们就继续深入聊聊如何使用Performance。通常使用Performance需要分三步走
1. 第一步是配置Performance
1. 第二步是生成报告页;
1. 第三步就是人工分析报告页,并找出页面的性能瓶颈。
接下来我会根据上面这三个步骤带你熟悉Performace并让你了解如何使用Performance来分析页面性能数据。
## 配置Performance
我们在Chrome中任意打开一个站点再打开Chrome开发者工具然后选择Performance标签最终效果如下图所示
<img src="https://static001.geekbang.org/resource/image/03/1c/036585de056964e025b9ed2ecd2bde1c.png" alt="">
上图就是Performance的配置页观察图中区域1我们可以设置该区域中的“Network”来限制网络加载速度设置“CPU”来限制CPU的运算速度。通过设置我们就可以在Chrome浏览器上来模拟手机等性能不高的设备了。在这里我将CPU的运算能力降低到了**1/6**,将网络的加载速度设置为“**快的3G(Fast 3G)**”用来模拟3G的网络状态。
不同于Audits只能监控加载阶段的性能数据Performance还可以监控交互阶段的性能数据不过Performance是分别录制这两个阶段的你可以查看上图区域2和区域3我们可以看到这里有两个按钮上面那个黑色按钮是用来记录交互阶段性能数据的下面那个带箭头的圆圈形按钮用来记录加载阶段的性能数据。
另外你还要注意一点,这两种录制方式稍微有点不同:
- 当你**录制加载**阶段的性能数据时Performance会重新刷新页面并等到页面完全渲染出来后Performance就会自动停止录制。
- 如果你是**录制交互阶段**的性能时,那么需要手动停止录制过程。
## 认识报告页
无论采用哪种方式录制,最终所生成的报告页都是一样的,如下图所示:
<img src="https://static001.geekbang.org/resource/image/89/e3/89164eac8a512e7a677e6e7bc88068e3.png" alt="">
观察上图的报告页,我们可以将它分为三个主要的部分,分别为**概览面板、性能指标面板和详情面板**。
要熟练掌握这三个面板我们需要先明白时间线的概念这是因为概览面板和性能指标面板都依赖于时间线。我们知道Performance按照时间的顺序来记录每个时间节点的性能数据然后再按照时间顺序来展示这些性能数据那么展示的时候就必然要引入时间线了。比如上图中我们录制了10000毫秒那么它的时间线长度也就是10000毫秒体现在上图中就是概览面板最上面那条线。
#### 1. 概览面板
好了,引入了时间线,**Performance就会将几个关键指标诸如页面帧速(FPS)、CPU资源消耗、网络请求流量、V8内存使用量(堆内存)等,按照时间顺序做成图表的形式展现出来,这就是概览面板**,你可以参看上图。
有了概览面板,我们就能一览几个关键的历史数据指标,进而能快速定位到可能存在问题的时间节点。那么如何定位可能存在问题的时间节点呢?
- 如果FPS图表上出现了红色块那么就表示红色块附近渲染出一帧所需时间过久帧的渲染时间过久就有可能导致页面卡顿。
- 如果CPU图形占用面积太大表示CPU使用率就越高那么就有可能因为某个JavaScript占用太多的主线程时间从而影响其他任务的执行。
- 如果V8的内存使用量一直在增加就有可能是某种原因导致了内存泄漏。
除了以上指标以外概览面板还展示加载过程中的几个关键时间节点如FP、LCP、DOMContentLoaded、Onload等事件产生的时间点。这些关键时间点体现在了几条不同颜色的竖线上。
#### 2. 性能面板
通常,我们通过概览面板来定位到可能存在问题的时间节点,接下来需要更进一步的数据,来分析导致该问题的原因,那么应该怎么分析呢?
这就需要引入**性能面板**了,在性能面板中,记录了非常多的性能指标项,比如**Main**指标记录渲染主线程的任务执行过程,**Compositor指标**记录了合成线程的任务执行过程,**GPU指标**记录了GPU进程主线程的任务执行过程。有了这些详细的性能数据就可以帮助我们轻松地定位到页面的性能问题。
简而言之,**我们通过概览面板来定位问题的时间节点,然后再使用性能面板分析该时间节点内的性能数据**。具体地讲比如概览面板中的FPS图表中出现了红色块那么我们点击该红色块性能面板就定位到该红色块的时间节点内了你可以参考下图
<img src="https://static001.geekbang.org/resource/image/8f/d5/8f029c84fb7606360a83c4a1f01627d5.png" alt="">
观察上图我们发现性能面板的最上方也有一段时间线比如上面这个时间线所展示的是从360毫秒到480毫秒这段时间就是我们所定位到的时间节点下面所展示的Network、Main等都是该时间节点内的详细数据。
如果你想要查看事件范围更广的性能指标你只需要将鼠标放到时间线上滚动鼠标滚轮就可以就行缩放了。如果放大之后要查看的内容如果超出了屏幕那么你可以点击鼠标左键来拖动时间线直到找到需要查看的内容你也可以通过键盘上的“WASD”四个键来进行缩放和位置的移动。
#### 3. 解读性能面板的各项指标
好了,现在我们了解性能面板,它主要用来展现**特定时间段内的多种性能指标数据**。那么要分析这些指标数据,我们就要明白这些指标数据的含义,不过要弄明白它们却并非易事,因为要很好地理解它们,**你需要掌握渲染流水线、浏览器进程架构、导航流程等知识点。**
因此在介绍性能指标之前,我们还需要岔开一下,回顾下这些前置的知识点。
因为浏览器的渲染机制过于复杂所以渲染模块在执行渲染的过程中会被划分为很多子阶段输入的HTML数据经过这些子阶段最后输出屏幕上的像素我们把这样的一个处理流程叫做**渲染流水线**。一条完整的渲染流水线包括了解析HTML文件生成DOM、解析CSS生成CSSOM、执行JavaScript、样式计算、构造布局树、准备绘制列表、光栅化、合成、显示等一系列操作。
渲染流水线主要是在渲染进程中执行的在执行渲染流水线的过程中渲染进程又需要网络进程、浏览器进程、GPU等进程配合才能完成如此复杂的任务。另外在渲染进程内部又有很多线程来相互配合。具体的工作方式你可以参考下图
<img src="https://static001.geekbang.org/resource/image/a4/09/a40850fbdfbfa4f95e1416b86bb24a09.png" alt="">
关于渲染流水线和浏览器进程架构的详细内容我在前面的章节中也做了很多介绍,特别是《[05 | 渲染流程HTML、CSS和JavaScript是如何变成页面的](https://time.geekbang.org/column/article/118205)》和《[06渲染流程HTML、CSS和JavaScript是如何变成页面的](https://time.geekbang.org/column/article/118826)》这两节,你可以去回顾下相关章节的课程内容。
好了,我们简要回顾了渲染流水线和浏览器的进程架构,那么现在回归正题,来分析下性能面板各个指标项的具体含义。你可以参考下图:
<img src="https://static001.geekbang.org/resource/image/8b/3f/8bb592424abcef144aea6cd663d3593f.png" alt="图片: https://uploader.shimo.im/f/hJhJWqCnFLg92XnR.png">
观看上图的左边我们可以看到它是由很多性能指标项组成的比如Network、Frames、Main等下面我们就来一一分析这些性能指标项的含义。
我们先看最为重要的**Main指标**它记录了渲染进程的主线程的任务执行记录在Perofrmace录制期间在渲染主线程上执行的所有记录都可以通过Main指标来查看你可以通过点击Main来展开主进程的任务执行记录具体你可以观察下图
<img src="https://static001.geekbang.org/resource/image/b4/03/b488c30b769f5289cd165c6844ebe803.png" alt="">
观察上图一段段横条代表执行一个个任务长度越长花费的时间越多竖向代表该任务的执行记录。通过前面章节的学习我们知道主线程上跑了特别多的任务诸如渲染流水线的大部分流程JavaScript执行、V8的垃圾回收、定时器设置的回调任务等等因此Main指标的内容非常多而且非常重要所以我们在使用Perofrmance的时候大部分时间都是在分析Main指标。Main指标的内容特别多我会在下一节对它做详细分析。
通过渲染流水线,我们知道了渲染主线程在生成层树(LayerTree)之后,然后根据层树生成每一层的绘制列表,我们把这个过程称为**绘制(Paint)**。在绘制阶段结束之后,渲染主线程会将这些绘列表制**提交(commit)<strong>给合成线程并由合成线程合成出来漂亮的页面。因此监控合成线程的任务执行记录也相对比较重要所以Chrome又在性能面板中引入了**Compositor指标</strong>,也就是合成线程的任务执行记录。
在合成线程执行任务的过程中还需要GPU进程的配合来生成位图我们把这个GPU生成位图的过程称为**光栅化**。如果合成线程直接和GPU进程进行通信那么势必会阻塞后面的合成任务因此合成线程又维护了一个**光栅化线程池(Raster)**用来让GPU执行光栅化的任务。因为光栅化线程池和GPU进程中的任务执行也会影响到页面的性能所以性能面板也添加了这两个指标分别是**Raster指标**和**GPU指标**。因为Raster是线程池所以如果你点开Raster项可以看到它维护了多个线程。
渲染进程中除了有主线程、合成线程、光栅化线程池之外还维护了一个IO线程具体细节你可以参考《[15 | 消息队列和事件循环:页面是怎么“活”起来的?](https://time.geekbang.org/column/article/132931)》这篇文章。该IO线程主要用来接收用户输入事件、网络事件、设备相关等事件如果事件需要渲染主线程来处理那么IO线程还会将这些事件转发给渲染主线程。在性能面板上**Chrome_ChildIOThread指标**对应的就是IO线程的任务记录。
好了以上介绍的都是渲染进程和GPU进程的任务记录除此之外性能面板还添加了其他一些比较重要的性能指标。
第一个是**Network指标**,网络记录展示了页面中的每个网络请求所消耗的时长,并以瀑布流的形式展现。这块内容和网络面板的瀑布流类似,之所以放在性能面板中是为了方便我们和其他指标对照着分析。
第二个是**Timings指标**用来记录一些关键的时间节点在何时产生的数据信息关于这些关键时间点的信息我们在上一节也介绍过了诸如FP、FCP、LCP等。
第三个是**Frames指标**,也就是浏览器生成每帧的记录,我们知道页面所展现出来的画面都是由渲染进程一帧一帧渲染出来的,帧记录就是用来记录渲染进程生成所有帧信息,包括了渲染出每帧的时长、每帧的图层构造等信息,你可以点击对应的帧,然后在详细信息面板里面查看具体信息。
第四个是**Interactions指标**,用来记录用户交互操作,比如点击鼠标、输入文字等交互信息。
#### 4. 详情面板
通过性能面板的分析,我们知道了性能面板记录了多种指标的数据信息,并且以图形的形式展现在性能面板上。
具体地讲比如主线程上执行了解析HTML(ParserHTML)的任务,对应于性能面板就是一个长条和多个竖条组成图形。通过上面的图形我们只能得到一个大致的信息,如果想要查看这些记录的详细信息,就需要引入**详情面板**了。
你可以通过在性能面板中选中性能指标中的任何历史数据然后选中记录的细节信息就会展现在详情面板中了。比如我点击了Main指标中的ParserHTML这个过程下图就是详情面板展现该过程的详细信息。
<img src="https://static001.geekbang.org/resource/image/61/94/6148b6e934c8cf21319b9cc5bb3c5094.png" alt="">
由于详情面板所涉及的内容很多而且每种指标的详细内容都有所不同所以本节我就不展开来讲了。另外你可以去Google的官方网站查看Performance的一些[基础使用信息](https://developers.google.com/web/tools/chrome-devtools/evaluate-performance/timeline-tool?hl=zh-CN)。
## 总结
好了,本节内容就介绍到这里,下面我来总结下本文的主要内容:
本节我们首先介绍了如何去配置Performance并生成报告页然后我们将焦点放在了如何解读报告页上。
之后我们介绍了报告页面主要分为三个部分,概览面板、性能面板和详情面板。
我们可以通过概览面板来定位问题的时间节点,然后再使用性能面板分析该时间节点内的性能数据。不过在分析数据时,我们需要弄明白性能面板内各项数据指标的含义,要了解这些,需要了解浏览器渲染流水线、浏览器的进程架构等知识点,因此结合这些知识点,我们接下来分析了性能面板的各项指标的含义。
其中最为重要的是Main指标它记录了渲染主线程上的任务执行情况不过这块细节内容会非常多所以我们会在下一节来介绍。
最后我们还介绍了每个指标项的内容都有详细数据,这些详细数据是通过详情面板来展现,你只需要通过性能面板点击相应的数据,就能通过详情面板来查看详细数据了。不过详情面板所涉及的数据也是非常多的,所以本文也就没对详情面板做过深的介绍了。
我把Performance比喻成一张网它能把我们在前面章节中很多知识点都网罗起来并应用到实践中。
## 思考题
那么今天留给你的任务是多找几个站点使用Performance来录制加载过程和交互过程并熟悉报告页面中的各项性能指标如果有遇到不明白的问题欢迎在留言区留言与我交流。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给更多的朋友。