一图胜千言
进程&线程的角度
渲染进程。
包裹标签页的容器。包含了多个线程,这些线程一起负责了页面显示到屏幕上的各个方面。
-
合成线程(Compositor Thread) 。这是最先被告知垂直同步事件(vsync event,操作系统告知浏览器刷新一帧图像的信号)的线程。它接收所有的输入事件。如果可能,合成线程会避免进入主线程,自己尝试将输入的事件(比如滚动)转换为屏幕的移动。它会更新图层的位置,并经由 GPU 线程直接向 GPU 提交帧来完成这个操作。如果输入事件需要进行处理,或者有其他的显示工作,它将无法直接完成该过程,这就需要主线程了。
-
主线程。运行js&渲染流程。主线程容易卡顿,很大程度上是因为它要做的事情太多了。
-
合成图块栅格化线程(Compositor Tile Worker) 。由合成线程派生的一个或多个线程,用于处理栅格化任务。即将页面内容一般按照屏幕大小分块生成位图。
GPU 进程。
这是一个单一的进程,为所有标签页和浏览器周边进程服务。当帧被提交时,GPU 进程会将分为图块的位图和其他数据(比如四边形顶点和矩阵)上传到 GPU 中,真正将像素显示到屏幕上。GPU 进程只有一个的线程,叫 GPU 线程,实际上是它做了这些工作。
主线程运行过程: 浏览器并不需要执行所有步骤,具体情况取决于哪些步骤是必需的
注意上图中 Recalc Styles 和 Layout 下方指向 requestAnimationFrame
的红色箭头。在代码中恰好触发这两个情况是完全可能的。这种情况叫做强制同步布局(或强制同步样式,Forced Synchronous Layout 和 Forced Synchronous Styles),就是会引起回流重绘,通常于性能不利。
-
开始新的一帧。垂直同步信号触发,开始渲染新的一帧图像。
-
输入事件的处理。从合成线程将输入的数据,传递到主线程的事件处理函数。
所有的事件处理函数
(touchmove
,scroll
,click
,input
,Timer
,window resize
)都应该最先触发,每帧触发一次,但也不一定这样;具体见js三座大山之异步五基于异步的js性能优化中的事件循环与一帧的关系
. -
requestAnimationFrame
。这是更新屏幕显示内容的理想位置,因为现在有全新的输入数据,又非常接近即将到来的垂直同步信号。其他的可视化任务,比如样式计算,因为是在本次任务之后,所以现在是变更元素的理想位置。如果你改变了 —— 比如说 100 个类的样式,这不会引起 100 次样式计算;它们会在稍后被批量处理。唯一需要注意的是,不要查询进行计算才能得到的样式或者布局属性(比如el.style.backgroundImage
或el.style.offsetWidth
)。如果你这样做了,会导致重新计算样式,或者布局,或者二者都发生,进一步导致强制同步布局,乃至布局颠簸。
-
解析 HTML(Parse HTML) 。处理新添加的 HTML,创建 DOM 元素。在页面加载过程中,或者进行
appendChild
操作后,你可能看到更多的此过程发生。 -
重新计算样式(Recalc Styles) 。为新添加或变更的内容计算样式。可能要计算整个 DOM 树,也可能缩小范围,取决于具体更改了什么。例如,更改 body 的类名影响可能很大,但是值得注意的是浏览器已经足够智能了,可以自动限制重新计算样式的范围。
-
布局(Layout) 。计算每个可见元素的几何信息(每个元素的位置和大小)。一般作用于整个文档,计算成本通常和 DOM 元素的大小成比例。
-
更新图层树(Update Layer Tree) 。这一步创建层叠上下文,为元素的深度进行排序。
-
Paint。过程分为两步:
第一步,对所有新加入的元素,或进行改变显示状态的元素,记录 draw 调用(这里填充矩形,那里写点字);
第二步是栅格化(Rasterization,见后文),在这一步实际执行了 draw 的调用,并进行纹理填充。Paint 过程记录 draw 调用,一般比栅格化要快,但是两部分通常被统称为“painting”。 -
合成(Composite) :图层和图块信息计算完成后,被传回合成线程进行处理。这将包括
will-change
、重叠元素和硬件加速的 canvas 等。 -
栅格化规划(Raster Scheduled)和栅格化(Rasterize):在 Paint 任务中记录的 draw 调用现在执行。过程是在合成图块栅格化线程(Compositor Tile Workers)中进行,线程的数量取决于平台和设备性能。例如,在 Android 设备上,通常有一个线程,而在桌面设备上有时有 4 个。栅格化根据图层来完成,每层都被分成块。
这个过程可以通过cpu运行 也可以通过gpu运行(GPU 栅格化)是一种降低绘制(paint)成本的方法。
-
帧结束:各个层的所有的块都被栅格化成位图后,新的块和输入数据(可能在事件处理程序中被更改过)被提交给 GPU 线程。
-
如果还有剩余时间:浏览器会调用requestIdleCallback方法。这里可以做一些优先级不高的非UI操作的任务。
-
发送帧:最后,但同样很重要的是,图块被上传到GPU。GPU 使用四边形和矩阵(所有常用的 GL 数据类型)将图块 draw 在屏幕上。
基于帧的工作原理,性能优化建议
1.减少js长时间执行,避免主线程被长时间占用
。
将这个情况转化为下面情况
或者可以利用webwork并行&requestIdleCallback空闲执行等
具体方案参考:js三座大山之异步五基于异步的js性能优化
2.避免渲染流程长时间执行,避免主线程被长时间占用
-
case1:减少布局属性更改
如果您更改“layout”属性,例如会更改元素几何图形(如宽度、高度或其位置)的属性(如
left
或top
CSS 属性),则浏览器需要检查所有其他元素并重排
页面。任何受影响的区域都需要重新绘制,并且最终绘制的元素也需要进行合成。 -
case2:缩短渲染路径
如果您更改了 CSS 中某个元素的“paint-only”属性(例如,
background-image
、color
或box-shadow
等属性),则无需执行布局步骤即可提交对页面的视觉更新。如果可能的话,省略布局步骤,可以避免开销高昂的布局工作,否则可能导致生成下一帧时出现明显的延迟。 -
case3:
让合成线程做更多的事情,减少主线程的压力
虽然它不运行JavaScript、布局、绘制等任何工作,但它是完全负责启动主线程工作,然后将帧发送到屏幕的线程。如果它不必等待输入事件处理程序,它可以直接将帧发送到gpu。如果您更改的属性既不需要布局也不需要绘制,则浏览器可以直接跳到合成步骤,不占用主线程。对于页面生命周期中的高压力点(例如动画或滚动),这是最便宜且最理想的方式。
3.用资源换性能
开启gpu硬件加速,占用更多的内存~