We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
原文链接
翻译自:The Anatomy of a Frame
经常被开发者问及像素渲染管道流的细节问题,它每一步是怎样触发的,何时触发的、为什么会触发。 这篇文章就是为了解答这些疑惑的,你将了解到像素是如何渲染到屏幕的。
注意:以下讲解基于 Blink 内核的 Chrome 浏览器。例如像layout 或 style calcs 等步骤运作在浏览器主线程中,这在大部分浏览器厂商中达成共识,但以下整体架构未必被其他厂商采纳。
Blink
Chrome
layout
style calcs
在这张图里涉及到太多的内容,可以大致分为两个大的部分。 (以下文字需要反复对照这张图,为了方便,作者很贴心的给了图片地址The Anatomy of a Frame。)
该流程包含许多线程负责处理不同的事物,确保让你的页面渲染到屏幕。 主要的线程有如下
该流程服务于上述的线程和浏览器其他的进程。当渲染流程处理完帧后,将触发 commit 事件。 GPU流程将瓷砖tiles 和其他数据(例如:quads and matrices)上传给 GPU,GPU 将像素输出到屏幕。 实际GPU流程包含一条单线程GPU Thread 负责与 GPU 通信。
commit
tiles
GPU
GPU Thread
现在分析下渲染流程中的线程。
当用户交互例如输入框输入数据,OS触发 vsync event 首先通知 Compositor Thread。 如果可以的话,该线程避免进入主线程,通过 GPU Thread 直接与 GPU 通信,把用户的交互行为渲染到屏幕上。 如果不可以(事件绑定了回调处理或其他可视化DOM操作),这时才需要主线程来处理。
vsync event
Compositor Thread
该线程执行着我们耳熟能详的工作,如JavaScript, styles, layout 和 paint 等。 由于大量的工作运行在该线程中,该线程是造成页面卡顿jank的"真凶"。
JavaScript
styles
paint
jank
Compositor Thread 会生成(spawn)一条或多条子线程 Compositor Tile Worker 来处理光栅化任务。
Compositor Tile Worker
PS:在某种程度上 Compositor Thread 才是 big boss ,虽然它不处理 JavaScript、Layout, Paint等任务。 但它全面的控制着主线程的初始化和把每帧运输到屏幕上的工作。
big boss
Layout
Paint
其实 Service Workers,Web Workers 也存在该过程中,只是有点复杂,暂且不表。
Service Workers
Web Workers
接下来分析下图中完整版主线程中的每个事件是干什么工作的。 有一点需要记住的是:在实际中并不是每一步都会执行。 例如没有新的 HTML 需要解析,Parse HTML 就不会执行。 前面提到的卡顿的优化,就是避免渲染管道中的事件反复执行或避免某部分的执行。 具体参考渲染性能优化的最佳实践课程。
HTML
Parse HTML
同样值得注意的是:styles 和 layout 底下的红色箭头指向 requestAnimationFrame。 requestAnimationFrame 能确保在一帧(其实是指渲染管道流)开始前执行完毕。 但如果你在 requestAnimationFrame 代码不注意的话,可能会触发styles,layout的提前执行, 导致强制同步重排/布局 Forced Synchronous Layout,极大的破坏页面性能。
requestAnimationFrame
styles,layout
强制同步重排/布局 Forced Synchronous Layout
PS:以前的一些库动画实现例如 jQuery 依赖 setTimeout 或 setInterval,可以的话尽量用 CSS3 和 requestAnimationFrame 替代。
jQuery
setTimeout
setInterval
CSS3
Frame Start
Vsync
Input event handlers
compositor thread
input
vsync
input data
dom
100
class
style calculations
el.style.backgroundImage
el.style.offsetWidth
recalc styles
DOM
appendChild
Recalc Styles
style
body
Update Layer Tree
draw calls
Rasterization
painting
Composite
layer
tile
will-change
overlapping elements
canvas
Raster Scheduled and Rasterize
Compositor Tile Workers
Android
layers
Frame End
Frame Ships
quads and matrices
requestIdleCallback
在主线程处理完一帧后还剩余一些处理空间的话 requestIdleCallback 会被触发。 这是一个非常好的机会来处理非必要的工作,如用户行为信息等采集。 如果你是一个新手,这里有份参考:Using requestIdleCallback。
值得一提,最新的 React 利用此api来优化性能。
React
上面流程提及 layer 有两中概念,以示区分。
层叠上下文 the Stacking Contexts。例如两个绝对定位的div,根据元素前后出现和z-index会产生层叠关系。 这一过程在 Update Layer Tree 中处理,确保正确的层叠顺序。
the Stacking Contexts
div
z-index
这在之后的流程中处理,更适用于已绘制的元素的概念。 提升元素 Compositor Layer 层级的方法
Compositor Layer
will-change: transform
transform: translateZ(0) /* 3D */
animation
GPU加速
Compositor Layers
上述主线程描述的过程差不多都发生在CPU中。只有最后一步,传输完后 tiles 在 GPU中处理。
CPU
然而在 Android 中有点不同,Compositor Tile Workers 光栅化 Rasterization 的操作,也在 GPU 中完成。 draw calls 作为 GL 命令在 GPU 着色器中执行。
这被称为 GPU Rasterization,这是一种减少 paint 成本的一种方式。 如果页面使用了GPU光栅化,能在Chrome DevTools 中的 FPS Meter 查看。
GPU Rasterization
Chrome DevTools
FPS Meter
如果你想学习如何避免卡顿 jank,想对性能优化方面有更高级的认知, 下面这些都能帮助你。
The text was updated successfully, but these errors were encountered:
No branches or pull requests
一帧剖析
原文链接
翻译自:The Anatomy of a Frame
经常被开发者问及像素渲染管道流的细节问题,它每一步是怎样触发的,何时触发的、为什么会触发。
这篇文章就是为了解答这些疑惑的,你将了解到像素是如何渲染到屏幕的。
注意:以下讲解基于
Blink
内核的Chrome
浏览器。例如像layout
或style calcs
等步骤运作在浏览器主线程中,这在大部分浏览器厂商中达成共识,但以下整体架构未必被其他厂商采纳。一图胜千言
流程
在这张图里涉及到太多的内容,可以大致分为两个大的部分。
(以下文字需要反复对照这张图,为了方便,作者很贴心的给了图片地址The Anatomy of a Frame。)
该流程包含许多线程负责处理不同的事物,确保让你的页面渲染到屏幕。
主要的线程有如下
该流程服务于上述的线程和浏览器其他的进程。当渲染流程处理完帧后,将触发
commit
事件。GPU流程将瓷砖
tiles
和其他数据(例如:quads and matrices)上传给GPU
,GPU
将像素输出到屏幕。实际GPU流程包含一条单线程
GPU Thread
负责与GPU
通信。渲染流程线程
现在分析下渲染流程中的线程。
当用户交互例如输入框输入数据,OS触发
vsync event
首先通知Compositor Thread
。如果可以的话,该线程避免进入主线程,通过
GPU Thread
直接与GPU
通信,把用户的交互行为渲染到屏幕上。如果不可以(事件绑定了回调处理或其他可视化DOM操作),这时才需要主线程来处理。
该线程执行着我们耳熟能详的工作,如
JavaScript
,styles
,layout
和paint
等。由于大量的工作运行在该线程中,该线程是造成页面卡顿
jank
的"真凶"。Compositor Thread
会生成(spawn)一条或多条子线程Compositor Tile Worker
来处理光栅化任务。PS:在某种程度上
Compositor Thread
才是big boss
,虽然它不处理JavaScript
、Layout
,Paint
等任务。但它全面的控制着主线程的初始化和把每帧运输到屏幕上的工作。
其实
Service Workers
,Web Workers
也存在该过程中,只是有点复杂,暂且不表。主线程
接下来分析下图中完整版主线程中的每个事件是干什么工作的。
有一点需要记住的是:在实际中并不是每一步都会执行。
例如没有新的
HTML
需要解析,Parse HTML
就不会执行。前面提到的卡顿的优化,就是避免渲染管道中的事件反复执行或避免某部分的执行。
具体参考渲染性能优化的最佳实践课程。
同样值得注意的是:
styles
和layout
底下的红色箭头指向requestAnimationFrame
。requestAnimationFrame
能确保在一帧(其实是指渲染管道流)开始前执行完毕。但如果你在
requestAnimationFrame
代码不注意的话,可能会触发styles,layout
的提前执行,导致
强制同步重排/布局 Forced Synchronous Layout
,极大的破坏页面性能。PS:以前的一些库动画实现例如
jQuery
依赖setTimeout
或setInterval
,可以的话尽量用CSS3
和requestAnimationFrame
替代。Frame Start
:Vsync
触发, 一帧开始。Input event handlers
:合成线程compositor thread
把input
数据传给主线程,处理事件回调。OS调度程序将尽最大努力合理调度事件回调(touchmove, scroll, click等),以及时响应用户交互。
即便如此,在用户交互和主线程处理事件获得响应之间多少会有些延迟。
requestAnimationFrame
因为它离
vsync
很近,可以就近获取input data
。所以这是操作dom
理想的地方,例如修改100
个元素的class
,并不会导致
100
次样式计算style calculations
,而是会在之后的管道流中批量处理。需要注意的是:你不能在查询任何已计算的样式或布局属性(例如
el.style.backgroundImage
,el.style.offsetWidth
)。如果你这么做了,就像图中红色箭头标明的一样,
recalc styles
或layout
或两者会提前执行,将导致强制布局,更糟会引起页面抖动。Avoid Large, Complex Layouts and Layout Thrashing。
Parse HTML
:任何新增的HTML
都会被处理,构建新的DOM
元素。你可以在页面加载或
appendChild
等操作中看到这一过程。Recalc Styles
:对于解析样式文件或class
或style
等样式操作都会引发样式计算。可能会重新渲染整棵样式树。具体取决于哪个元素的样式改变,例如
body
就影响比较大。值得注意的是,浏览器已经很聪明能自动限制波及的范围。Layout
:计算可见元素几何(盒模型)信息(位置、尺寸)。通常会对整个文档操作一遍。产生的开销和
DOM
个数成正比例关系。Update Layer Tree
:创建层叠上下文和元素层级顺序。Paint
:其实这是绘画的第一步,这一步记录需要调用绘制的方法draw calls
(fill a rectangle here, write text there)。第二步是光栅化
Rasterization
(下面会提到),draw calls
会被执行。第一步显然速度要快于第二步,但经常把这两步都成为
painting
。Composite
:层layer
和瓷砖tile
信息被计算后回传给compositor thread
处理。Composite
负责处理有will-change
,overlapping elements
,或任何硬件加速的canvas
。Raster Scheduled and Rasterize
:光栅调度和光栅化,这里将执行在Paint
任务中提到的draw calls
。将在
Compositor Tile Workers
中处理,该线程的多少取决于系统和硬件设备的能力。例如
Android
通常起一个Compositor Tile Workers
线程,PC 可能有4个。根据层
layers
信息来光栅化,layers
是由很多瓷砖tiles
组成。Frame End
:所有layers
中被光栅化的tiles
和input data
(可能被事件回调处理了)将被提交给GPU Thread
。Frame Ships
:最后,所有的瓷砖tiles
都将被GPU Thread
上传给硬件GPU
处理。GPU
将使用quads and matrices
来把tiles
打印在屏幕上。requestIdleCallback
在主线程处理完一帧后还剩余一些处理空间的话
requestIdleCallback
会被触发。这是一个非常好的机会来处理非必要的工作,如用户行为信息等采集。
如果你是一个新手,这里有份参考:Using requestIdleCallback。
值得一提,最新的
React
利用此api来优化性能。LAYERS AND LAYERS
上面流程提及
layer
有两中概念,以示区分。层叠上下文
the Stacking Contexts
。例如两个绝对定位的div
,根据元素前后出现和z-index
会产生层叠关系。这一过程在
Update Layer Tree
中处理,确保正确的层叠顺序。这在之后的流程中处理,更适用于已绘制的元素的概念。
提升元素
Compositor Layer
层级的方法will-change: transform
/* transform 不支持的 hack */transform: translateZ(0) /* 3D */
对于层级少
animation
元素能减少性能开销(避免主线程管道流中的某部分执行),俗称GPU加速
。但浏览器可能不得不创建额外的
Compositor Layers
来保存层叠顺序(z-index指定),这就是产生了
overlapping elements
元素。发散主题 RIFFING ON A THEME
上述主线程描述的过程差不多都发生在
CPU
中。只有最后一步,传输完后tiles
在GPU
中处理。然而在
Android
中有点不同,Compositor Tile Workers
光栅化Rasterization
的操作,也在GPU
中完成。draw calls
作为 GL 命令在GPU
着色器中执行。这被称为
GPU Rasterization
,这是一种减少paint
成本的一种方式。如果页面使用了GPU光栅化,能在
Chrome DevTools
中的FPS Meter
查看。参考
如果你想学习如何避免卡顿
jank
,想对性能优化方面有更高级的认知,下面这些都能帮助你。
The text was updated successfully, but these errors were encountered: