做性能優(yōu)化時,我們關(guān)注哪些指標(biāo)?
大廠技術(shù)??爆肝日更??精選好文
前一段時間,我們對 webview 做了一期性能優(yōu)化,在優(yōu)化過程中,我們追求的是頁面要足夠「快」「穩(wěn)」。那怎么定量地衡量這個「快」「穩(wěn)」呢?性能指標(biāo)幫助我們從數(shù)據(jù)化角度了解頁面性能現(xiàn)狀,性能瓶頸以及優(yōu)化完成后,衡量優(yōu)化效果。
性能指標(biāo)在日常開發(fā)中,大家或多或少的都有接觸。比如?? 是一個頁面在 Lighthouse 下跑出來的性能數(shù)據(jù)。

這篇文章將回答下面幾個問題:
- 哪些性能指標(biāo)是需要觀測的?它們是什么含義?
- 這么多指標(biāo),我們在什么場景下應(yīng)該關(guān)注哪些?
- 指標(biāo)是怎么采集的?
- First Paint
- First Contentful Paint
- Largest Contentful Paint
- First Meaningful Paint
- First Input Delay
- Cumulative Layout Shift
- Time to Interactive
- DOMContentLoaded
- Load
First Paint(FP) 首次繪制
首次渲染的時間點,可以視為白屏?xí)r間,比如完成背景色渲染的時間點。通常作為時間點最早的一個性能指標(biāo)。
First Contentful Paint(FCP) 首次內(nèi)容繪制
首次有內(nèi)容渲染的時間點,指標(biāo)測量頁面從開始加載到頁面內(nèi)容的任何部分在屏幕上完成渲染的時間。對于該指標(biāo),"內(nèi)容"指的是文本、圖像、元素或非白色的元素。可以作為首屏?xí)r間。
FP vs FCP
FP:從開始加載到第一次渲染
FCP:從開始加載到第一次內(nèi)容渲染。
FCP 是 FP 的增強版,對用戶來說更關(guān)鍵。因為 FCP 帶著圖像和文字這些內(nèi)容信息,是用戶更關(guān)心的。
FP 和 FCP 可能是重合的。

Largest Contentful Paint (LCP) 最大內(nèi)容繪制
頁面的最大內(nèi)容(通常是比較核心的內(nèi)容)加載完成的時間,這個最大內(nèi)容可以是圖片/文本塊。它是一個 SEO 相關(guān)的指標(biāo)。
LCP vs FCP
FCP:頁面加載過程中,比較早期的一個指標(biāo),如果一個頁面有 loading 態(tài),這個指標(biāo)表現(xiàn)可能很好,但是實際內(nèi)容什么時候呈現(xiàn)給用戶,這個指標(biāo)沒辦法衡量。
LCP:關(guān)注頁面核心內(nèi)容呈現(xiàn)時間,這個內(nèi)容是用戶更感興趣的,更加用戶相關(guān)。

First Meaningful Paint(FMP) 首次有效繪制
首次繪制有意義內(nèi)容的時間。業(yè)界比較認可的方式是在加載和渲染過程中最大布局變動之后的那個繪制時間即為當(dāng)前頁面的 FMP。因為它計算相對復(fù)雜,且存在準(zhǔn)確性等問題,Lighthouse 6.0 中被廢棄。
LCP vs FMP
FMP: 早期比較推薦的性能指標(biāo),但是計算更復(fù)雜,而且準(zhǔn)確性不是很好
LCP: 更新的數(shù)據(jù)指標(biāo),有 API 直接支持,統(tǒng)計簡單,且準(zhǔn)確,但也存在最大內(nèi)容是否為最核心內(nèi)容這樣的問題。
First Input Delay(FID) 首次輸入延遲
這個指標(biāo)的觸發(fā)是在用戶第一次與頁面交互的的時候,記錄的是是用戶第一次與頁面交互到瀏覽器真正能夠開始處理事件處理程序以響應(yīng)該交互的時間,即交互延遲時間。比如發(fā)生在用戶第一次在頁面進行 click, keydown 等交互。
為什么會有這樣的延遲呢?一般來說,發(fā)生輸入延遲是因為瀏覽器的主線程正忙著執(zhí)行其他工作(比如解析和執(zhí)行大型 JS 文件),還不能響應(yīng)用戶。
Cumulative Layout Shift(CLS) 累積布局偏移
在一個頁面的生命周期中,會不斷的發(fā)生布局變化(layout shift),對每一次布局變化做一個累積的記分,其中得分最大布局變化即為 CLS。是衡量頁面穩(wěn)定性的重要指標(biāo)(visual stability)
糟糕的 CLS 對用戶體驗的影響??

Core Web Vitals
2020 年 5 月,Google 提出的衡量網(wǎng)站用戶體驗的核心數(shù)據(jù)指標(biāo),涵蓋了頁面的加載速度、可交互性和穩(wěn)定性。是近期生效的會影響 SEO 的重要指標(biāo),包含一下三項:
- LCP
- FID
- CLS
Time to Interactive(TTI) 可交互時間
說到 TTI 首先要介紹下 Long Task
Long Task:如果瀏覽器主線程執(zhí)行的一個 task 耗時大于 50ms,那么這個 task 稱為 long task。用戶的交互操作也是在主線程執(zhí)行的,所以當(dāng)發(fā)生 Long Task 時,用戶的交互操作很可能無法及時執(zhí)行,這時用戶就會體驗到卡頓(當(dāng)頁面響應(yīng)時間超過 100ms 時,用戶可以體驗到卡頓),進而影響用戶體驗。
從頁面加載開始到頁面處于完全可交互狀態(tài)所花費的時間。通常是發(fā)生在頁面依賴的資源已經(jīng)加載完成,此時瀏覽器可以快速響應(yīng)用戶交互的時間。
DOMContentLoaded(DCL)
DOM 加載完成即觸發(fā),不用等頁面資源加載。
Load(L)
頁面及其依賴的資源全部加載完成的時間,包括所有的資源文件,如樣式表和圖片等。
常見的性能名詞以用戶為中心的性能指標(biāo) (user-centric metric)
傳統(tǒng)性能指標(biāo):
很長時間以來,描述性能是通過 Load/DOMContentLoaded 事件進行測量的,一個網(wǎng)站加載完成的時間是多少秒。雖然 Load/DOMContentLoaded 是頁面生命周期中比較明確的時刻,但是它真的能很好的反應(yīng)實際用戶訪問頁面的真實感受嗎?
比如,服務(wù)器如果響應(yīng)一個很小的體積的頁面,Load/DOMContentLoaded 會很快觸發(fā),然后異步獲取內(nèi)容,之后在頁面上顯示。這樣的頁面 Load/DOMContentLoaded 的時間很短,那從用戶體驗角度講它的性能表現(xiàn)就是好的嗎?
要回答上面問題,也就引出了?? 這個概念。
以用戶為中心的性能指標(biāo) (user-centric metric):
以用戶為中心的性能指標(biāo)更關(guān)注從用戶角度看,頁面的性能是怎樣的?頁面呈現(xiàn)的內(nèi)容是不是滿足用戶需要,用戶交互起來是否流暢等。上面介紹的 FCP、LCP、FMP、FID、CLS、TTI 均是以用戶為中心的性能指標(biāo)
| 是否正在發(fā)生? | 導(dǎo)航是否成功啟動?服務(wù)器有響應(yīng)嗎? |
|---|---|
| 是否有用? | 是否渲染了足夠的內(nèi)容讓用戶可以深入其中? |
| 是否可用? | 頁面是否繁忙,用戶是否可以與頁面進行交互? |
| 是否令人愉快? | 交互是否流暢自然,沒有延遲和卡頓? |
根據(jù)采集方式不同,性能指標(biāo)的幾種形式
根據(jù)測量方式的不同,性能數(shù)據(jù)可以分為:Lab Data 和 Field Data
Lab Data / SYN
SYN 即 synthetic monitoring,收集形式也有叫 in the lab
Lab Data 是在可控的條件下,特定的機型,特定的網(wǎng)絡(luò)環(huán)境,收集的性能數(shù)據(jù)。一個使用場景是,新頁面開發(fā)的時候,頁面發(fā)布到生產(chǎn)環(huán)境中之前,是沒辦法基于真實用戶做性能指標(biāo)測量的,此時,想了解也沒性能情況,可以通過 Lab 方式收集和檢查。
Field Data / RUM
RUM 即 Real User Monitoring,收集形式也有叫 in the Filed
Lab 的方式測量雖然能反應(yīng)性能情況,但是真實用戶因機型和網(wǎng)絡(luò)情況各異,頁面加載對于不同用戶具有很大的不確定性,Lab 數(shù)據(jù)并不一定是真實用戶的實際情況。而 filed 數(shù)據(jù)很好的解決了這個問題,有一定的代碼侵入性,記錄真實用戶的性能數(shù)據(jù),通過 RUM 數(shù)據(jù)可以發(fā)現(xiàn)一些 Lab 數(shù)據(jù)下很難暴露出來的性能異常。
RAIL Model
我們先來看下 RAIL 這幾個字母分別對應(yīng)什么?
R: response
A: animation
I: idle
L: load
RAIL 是 Google 提出的以用戶為中心的一套性能模型,從各個維度反應(yīng)一個網(wǎng)站的性能情況,同時提供一組性能目標(biāo)供參考??
- Response: 50ms 內(nèi)對事件做出響應(yīng)
- Animation: 動畫 10 ms 內(nèi)生成內(nèi)一幀(每幀耗時 16ms,用戶會感到動畫流暢,為什么這里是 10ms?)
- Idle: 最大化的利用上空閑時間
- Load: 在 5s 內(nèi)完成內(nèi)容傳輸并達到用戶可交互
性能指標(biāo)相關(guān)的 API 及采集方式
Performance Observer[1]
Performance Observer API 下包含一組性能監(jiān)測相關(guān)的 API
- Paint Timing API
- Largest Contentful Paint API
- Event Timing API
- Navigation Timing API
- Layout Instability API
- Long Tasks API
- Resource Timing API
下面按照不同指標(biāo)的收集用到的 API 依次介紹它們是怎么用的。
Paint Timing API[2]
用于收集 FP / FCP
new?PerformanceObserver((list)?=>?{
??for?(const?entry?of?list.getEntries())?{
????console.log('FCP:?',?entry.startTime);
??}
}).observe({
??type:?'first-contentful-paint',
??buffered:?true,
});
new?PerformanceObserver((list)?=>?{
??for?(const?entry?of?list.getEntries())?{
????console.log('FP:?',?entry.startTime);
??}
}).observe({
??type:?'first-paint',
??buffered:?true,
});
Largest Contentful Paint API[3]
用于收集 LCP
largest-contentful-paint 事件會在頁面加載過程中根據(jù)此時已渲染最大元素的變化,不斷的被觸發(fā),實際上報中會一直監(jiān)聽這些變化,直到用戶與頁面發(fā)生交互行為(比如 click、keydown)或者頁面被隱藏或者頁面被 unload 等,取監(jiān)聽到的最后值做上報。
new?PerformanceObserver(entryList?=>?{
??for?(const?entry?of?entryList.getEntries())?{
????console.log('LCP:?',?entry.startTime);
??}
}).observe({
????type:?'largest-contentful-paint',
????buffered:?true
});





完整版代碼參考?? https://github.com/GoogleChrome/web-vitals/blob/main/src/getLCP.ts
這個指標(biāo)是 Core Web Vitals,但是兼容性不好,iOS 下都是不支持的??

Event Timing API[4]
用于收集 FID
監(jiān)聽用戶的第一次輸入(first-input)事件,F(xiàn)ID = 開始處理 input 的時間 - input 操作的起始時間
new?PerformanceObserver(list?=>?{
??for?(const?entry?of?list.getEntries())?{
????//?開始處理?input?的時間?-?input?操作的起始時間
????const?FID?=?entry.processingStart?-?entry.startTime;?
????console.log('FID:',?FID);
??}
}).observe({
??type:?'first-input',
??buffered:?true,
});
Navigation Timing 1.0[5] 或 Navigation Timing 2.0[6]
用于收集 Load / DOMContentLoaded

new?PerformanceObserver(list?=>?{
??for?(const?entry?of?list.getEntries())?{
????const?Load?=?entry.loadEventStart?-?entry.fetchStart;
????console.log('Load:',?Load);
??}
}).observe({
??type:?'navigation',
??buffered:?true,
});
new?PerformanceObserver(list?=>?{
??for?(const?entry?of?list.getEntries())?{
????const?DOMContentLoaded?=?entry.domContentLoadedEventStart?-?entry.fetchStart;
????console.log('DOMContentLoaded:',?DOMContentLoaded);
??}
}).observe({
??type:?'navigation',
??buffered:?true,
});
Layout Instability API[7]
收集 CLS: 將加載過程分塊(session),監(jiān)聽 layout-shift 變化獲得每次布局變動的 value 值,統(tǒng)計每個 session 的布局變動分數(shù),最大的布局變動分數(shù)時段,即為 CLS。
let?sessionEntries?=?[];
let?sessionValue?=?0;
let?metric?=?{
??value:?0
}
new?PerformanceObserver(entryList?=>?{
??for?(const?entry?of?entryList.getEntries())?{
????if?(!entry.hadRecentInput)?{
??????const?firstSessionEntry?=?sessionEntries[0];
??????const?lastSessionEntry?=?sessionEntries[sessionEntries.length?-?1];
??????//?如果時間靠近上一個?session,?將本輪?layout-shift?累加進上一個?session
??????if?(sessionValue?&&
??????????entry.startTime?-?lastSessionEntry.startTime?1000?&&
??????????entry.startTime?-?firstSessionEntry.startTime?5000)?{
????????sessionValue?+=?entry.value;
????????sessionEntries.push(entry);
??????}?else?{?//?新起一個?session
????????sessionValue?=?entry.value;
????????sessionEntries?=?[entry];
??????}
??????//?如果當(dāng)前?session?的?value?大于之前的最大值,替換為現(xiàn)在這個大的
??????if?(sessionValue?>?metric.value)?{
????????metric.value?=?sessionValue;
????????metric.entries?=?sessionEntries;
????????console.log('CLS:?',?metric)
??????}
????}
??}
}).observe({
????type:?'layout-shift',
????buffered:?true
});
完整代碼見?? https://github.com/GoogleChrome/web-vitals/blob/main/src/getCLS.ts
Long Tasks API[8] & Resource Timing API[9]
TTI 的采集依賴這兩個 API,計算過程:
- 先采集 FCP,作為起點
- 沿時間軸正向搜索時長至少為 5 秒的安靜窗口(安靜窗口:沒有 Long Task 且不超過兩個正在處理的網(wǎng)絡(luò) GET 請求)
- 沿時間軸反向搜索安靜窗口之前的最后一個長任務(wù),如果沒有找到長任務(wù),則在 FCP 步驟停止執(zhí)行
- TTI 是安靜窗口之前最后一個長任務(wù)的結(jié)束時間,如果沒有找到長任務(wù),則與 FCP 值相同
—— Time to Interactive (TTI)[10]

Mutation[11][12]Observer[13]
收集 FMP:通過 MutationObserver 對 DOM 變化進行監(jiān)聽,每次回調(diào)根據(jù)新舊 DOM 數(shù)量、種類、深度等,計算出當(dāng)前 DOM 樹的分數(shù),分數(shù)變化最劇烈的時刻視為 FMP ,Load 事件觸發(fā)后 200ms 停止監(jiān)聽,取最大變動的記錄做上報。
new?MutationObserver(()?=>?{
??//?這里是劇烈程度分的計算
}).observe(document,?{
??childList:?true,
??subtree:?true,
});
業(yè)界性能指標(biāo)采集工具
In the Lab
- Chrome DevTools[14]
- Lighthouse[15]
- WebPageTest[16]
In the Field
- web-vitals[17]
Web Vitals[18]
Largest Contentful Paint (LCP)[19]
Cumulative Layout Shift (CLS)[20]
First Input Delay (FID)[21]
Measure performance with the RAIL model[22]
參考資料
[1]Performance Observer: https://www.w3.org/TR/performance-timeline-2/
[2]Paint Timing API: https://w3c.github.io/paint-timing/#sec-PerformancePaintTiming
[3]Largest Contentful Paint API: https://wicg.github.io/largest-contentful-paint/
[4]Event Timing API: https://wicg.github.io/event-timing/
[5]Navigation Timing 1.0: https://www.w3.org/TR/navigation-timing/
[6]Navigation Timing 2.0: https://www.w3.org/TR/navigation-timing-2/
[7]Layout Instability API: https://wicg.github.io/layout-instability/
[8]Long Tasks API: https://www.w3.org/TR/2017/WD-longtasks-1-20170907/
[9]Resource Timing API: https://www.w3.org/TR/resource-timing-2/
[10]Time to Interactive (TTI): https://web.dev/tti/
[11]Mutation: https://dom.spec.whatwg.org/#mutationobserver
[12]: https://dom.spec.whatwg.org/#mutationobserver
[13]Observer: https://dom.spec.whatwg.org/#mutationobserver
[14]Chrome DevTools: https://developers.google.com/web/tools/chrome-devtools/
[15]Lighthouse: https://developers.google.com/web/tools/lighthouse/
[16]WebPageTest: https://www.webpagetest.org/
[17]web-vitals: https://github.com/GoogleChrome/web-vitals
[18]Web Vitals: https://web.dev/vitals/
[19]Largest Contentful Paint (LCP): http://web.dev/lcp
[20]Cumulative Layout Shift (CLS): https://web.dev/cls/
[21]First Input Delay (FID): https://web.dev/fid/
[22]Measure performance with the RAIL model: https://web.dev/rail/
往期推薦
六個問題讓你更懂 React Fiber,了解框架底層渲染邏輯
點擊下方“技術(shù)漫談”,選擇“設(shè)為星標(biāo)”第一時間關(guān)注技術(shù)干貨!
