我們一般判斷一個網站性能好不好,一般通過一些主觀感受:使用是否流暢,有沒有卡頓之類的
然而,這種方法缺乏客觀性和可衡量性
基本概念
造成卡頓的原因
前置知識:幀率&刷新率
● 幀率(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時間條,摘要中會顯示代碼所在的具體位置