我们一般判断一个网站性能好不好,一般通过一些主观感受:使用是否流畅,有没有卡顿之类的
然而,这种方法缺乏客观性和可衡量性
基本概念
造成卡顿的原因
前置知识:帧率&刷新率
● 帧率(FPS):指的是一秒内合成帧的数量,常用 FPS 来描述,60FPS 表示一秒内合成 60 帧。
● 刷新率(Hz):指的是一秒内屏幕的刷新次数。刷新率越高,屏幕更新图像的次数就越频繁,这通常会导致更平滑的视觉效果和更少的运动模糊。是由显示器配置决定的参数,最常见的刷新率为60Hz
当帧率和刷新率保持一致时,才能提供最平滑的视觉体验
卡顿现象可以比较明确的分为三个类型,分别是“丢帧”、“画面撕裂”、“长时间未响应”
● 丢帧:当帧率小于刷新率时,屏幕内出现了连续两帧使用同一帧数据渲染,给人以停顿和不流畅的感觉。
● 画面撕裂:在屏幕的刷新率与图形处理单元(GPU)输出到屏幕的帧率不同步时发生。这意味着屏幕正在刷新显示的内容时,GPU就发送了新的图像数据,导致屏幕上的上半部分和下半部分显示了两个不同时间点的帧
- 长时间未响应:指的是画面长时间等待,等待网络请求或其他事件处理,这种情况通常并不是由于帧渲染导致的,但等待时长如果违背了 RAIL 模型,会严重阻塞(BLOCK)用户行为
使用 RAIL 模型衡量性能
- Response(响应):在50ms内完成处理, 在 100 毫秒内响应用户的输入,让用户感觉互动是瞬时完成的。
- Animation(动画):在10ms内生成一帧
从技术上讲,每一帧的最大预算为 16 毫秒(1000 毫秒 / 60 帧/秒约 16 毫秒),但浏览器需要大约 6 毫秒才能渲染每一帧,因此准则是每帧 10 毫秒。
- Idle(空闲):最大化空闲时间,以提高网页可以在 100 毫秒内响应用户输入的几率
- Load(加载):根据用户的设备和网络功能进行优化,以实现快速加载性能。目前,在 3G 网速较慢的中端移动设备上加载网页并可在 5 秒或更短的时间内加载网页
帧&刷新频率
目前,大多数设备的刷新频率为60HZ(每秒 60 次刷新)。每次刷新都会生成您看到的视觉输出,通常称为“帧”。
<html>
<head>
<style>
#box{
font-size: 200px;
}
</style>
</head>
<body>
<h1 id="box">0</h1>
<script>
const box = document.getElementById('box');
let count = 0;
function add() {
count++;
box.innerHTML = count;
requestAnimationFrame(add)
}
add()
</script>
</body>
</html>
鉴于典型的显示屏每秒刷新 60 次,一些简单的数学运算结果显示浏览器有 16.66 毫秒的时间来生成每一帧。
let count = 0;
function add() {
count++;
box.innerHTML = count;
setTimeout(add, 16.6)
}
但是不同设备的刷新频率是不同的,如果在刷新频率为144HZ上的设备上进行开发时(1000/144 ≈ 7ms)发现setTimeout设为7ms并不会卡顿,但是在刷新频率为60hz的设备上调试时,就会出现一些异常现象。如下有一些数字消失了
所以如果我们像确保在屏幕上连续显示每个数字,最好是使用 requestAnimationFrame,因为它会根据浏览器的渲染帧率来调用回调函数
但实际上,浏览器对于每一帧都有自己的开销,以刷新频率60HZ为例,所有工作都需要在 10 毫秒内完成。如果无法达到此预算,帧速率会下降,并且网页内容会在屏幕上发生抖动。这种现象通常称为“卡顿”。
渲染流程
浏览器的渲染流程又称为渲染管道或像素管道,完整的渲染流程如下:
以下面代码举例说明
//html
<div id="box"></div>
//css
#box {
background: red;
}
//js
const box = document.getElementById("box")
box.style.width = "50px"
box.style.height = "50px"
- JavaScript:JavaScript 通常用于处理会使界面发生视觉变化的工作。
box.style.width = "50px"
box.style.height = "50px"
- style:样式计算,计算出哪些 CSS 规则应用于哪些 HTML 元素的过程。
上面代码在style步骤会计算出整个html所有元素的最终的样式,包括body、div,其中div的样式计算结果为
background: red;
width: 50px
height: 50px
//...一些默认的样式
-
Layout: 布局计算,计算页面的几何图形,如元素占据了多少空间以及元素在屏幕上的显示位置
image.png -
Paint:绘制,绘制是填充像素的过程。它涉及到在计算完元素在网页上的布局后绘制文本、颜色、图片、边框、阴影,基本上还包括元素的每个视觉方面。绘制通常在多个表面(通常称为图层)上完成
image.png -
Composite:合成,由于网页的某些部分可能会绘制到多个图层上,因此它们需要以正确的顺序应用到屏幕上,才能使网页按预期呈现
image.png
针对渲染流程的每一步优化
Performance面板
当应用有卡顿现象就可以使用Performance面板定位问题,它能快速定位页面渲染过程中各个环节的耗时,如果发现某个Activity耗时明显异常,就得逐步去排查对应位置的代码了
使用performance测试时要注意:
使用无痕模式打开:无痕模式可确保 Chrome 在干净的状态下运行。如果安装了许多扩展程序,这些扩展程序可能会在性能测量结果中产生噪声。
-
cpu减速来测试极限条件:与台式机和笔记本电脑相比,移动设备的 CPU 性能要低得多。每次分析网页时,您都可以使用 CPU 节流来模拟网页在移动设备上的性能
image.png -
开启屏幕截图:开启屏幕截图后会把每一帧的渲染结果显示出来
image.png
缩略图
-
FPS图表:如果您看到 FPS 以上出现红条,则表示帧速率下降得非常低,以至于可能会影响用户体验。一般来说,绿条越多,FPS 越高。
image.png -
CPU图表:CPU 图表中的颜色对应于“性能”面板底部的摘要标签页中的颜色。CPU 图表是全彩色的这一事实意味着,在记录期间 CPU 已用尽。只要发现 CPU 长时间达到上限,系统就会提示设法减少工作量。下面的有Summary标签页详细的展示了CPU的详细花销
image.png -
屏幕截图
image.png
火焰图
介绍
参考:
软件的性能分析,往往需要查看 CPU 耗时,了解瓶颈在哪里。火焰图就是性能分析的利器
通过分析主要任务,可以知道哪一步导致了卡顿
y 轴表示调用栈,每一层都是一个函数。调用栈越深,火焰就越高,顶部就是正在执行的函数,下方都是它的父函数。
x 轴表示抽样数,如果一个函数在 x 轴占据的宽度越宽,就表示它被抽到的次数多,即执行的时间长。注意,x 轴不代表时间,而是所有的调用栈合并后,按字母顺序排列的。
火焰图就是看顶层的哪个函数占据的宽度最大。只要有"平顶"(plateaus),就表示该函数可能存在性能问题。
颜色没有特殊含义,因为火焰图表示的是 CPU 的繁忙程度,所以一般选择暖色调。
浏览器的火焰图
浏览器的火焰图与标准火焰图有两点差异:
● 它是倒置的(即调用栈最顶端的函数在最下方)
● x 轴是时间轴,而不是抽样次数。
案例
下面代码通过while循环来占领线程
function printNumber(max) {
let n = 0;
while(++n < max) {
console.log(n)
}
}
function testA() {
return printNumber(20000)
}
function testB() {
return printNumber(10000)
}
testA()
testB()
性能图如下
上面案例生成的性能图中可以通过上方的缩略图大致找到有性能问题的地方
-
CPU图表:CPU 图表中的颜色对应于“性能”面板底部的摘要标签页中的颜色。CPU 图表是全彩色的这一事实意味着,在记录期间 CPU 已用尽
image.png -
FPS图表: 出现红条则说明前刷新频率小于60HZ,可能发生了卡顿
image.png
● 红条:长帧(当前刷新频率小于60HZ),可能发生了卡顿
● 绿色:绿色的柱越高,刷新频率越大,说明越流畅
● 白色:当前帧没有发生变化
再根据缩略图对应的火焰图定位产生性能问题的地方
每个条形都代表一个事件,横条越宽,表示事件用时越长。y 轴表示调用堆栈。如果您看到事件相互堆叠,则表示上层事件导致了下层事件。
从图中可以看出,长任务的原因是因为在解析html,解析html的全部时间花在了评估脚本(执行js),执行JS是因为某个匿名函数,这个匿名函数花销全部用在执行testA和testB,最后再找到testA和testB下面在执行printNumber花费了所有时间,所以最终定位到printNumber方法上。
点击printNumber时间条,摘要中会显示代码所在的具体位置