提到 Web 前端的效能優化,有許多的技巧是聚焦在如何減少頁面的「載入時間 Loading Time」,例如 Code Splitting 透過減少需要載入的 Bundle Size 來加快載入效能。也有些技巧是針對執行時期 (Runtime) 的優化與調教,例如 Virtualized List 透過控制渲染的 DOM 元素數量來保持頁面的流暢性,又或者是頁面的 Repaint、Reflow、 Composite 等渲染流程所花費的時間,不過這些 runtime 指標又該如何 debug 呢?什麼樣的狀況又代表著頁面的效能可能出現了一些瓶頸呢?在現今網頁中動畫佔了十分重要的部分,那動畫的效能又該怎麼觀測呢?
今天想透過這篇文章與各位分享如何透過 Chrome Devtool 的 Performance Tab 來檢測網頁在執行時的各種效能指標,讓網頁的 Runtime Performance 不再成為你 debug 時的瓶頸!
如果各位讀者對於 Web 前端的效能優化十分感興趣,可以閱讀我在今年鐵人賽的系列文「今晚,我想來點前端效能優化大補帖!」,我想能夠幫助你對於前端效能優化有個蠻全面的基本理解。
至於本文中會拿來做 debug 示範的範例網站則是出自鐵人賽系列文的其中一篇文章,若是想了解來龍去脈的讀者也可以前往閱讀。(沒看過不影響本文的閱讀)
有使用過 Chrome Devtool Performance Tab 的讀者可能曾經也和我一樣被豐富的圖表與複雜的資訊給嚇到了,完全不知道要從何開始看起。的確 Chrome 的 Performance Tab 提供了相當豐富的資訊,要在一篇文章就整理透徹幾乎可以說是不可能的任務,所以今天只會介紹最基本的資訊與圖表,但我認為也已經足夠面對平常 Debug 時的需求。那我們就廢話不多說,直接開始吧!
首先開啟一個無痕視窗並造訪今天作為範例的網站,使用無痕視窗的原因是可以確保 chrome 是運行在「clean state」下的,不然 performance 的測量結果有可能被 extensions 等正在運行的其他應用影響,造成不夠準確的狀況。
接著在鍵盤輸入 Command+Option+I (Mac) 或是 Control+Shift+I (Windows, Linux) 打開 Devtool 並點選 Performance Tab。
左上角會有兩個按鈕(紅色框框區塊),點選第一個即會開始紀錄,這時候你可以開始操作網頁,Devtool 會紀錄操作網頁時的 CPU、記憶體、Frame Rate 等使用量與指標,這種方式適合監測頁面上的某些特定行為與功能。
第二顆按鈕「Start profiling and reload page」,會開始 profiling 並重新整理頁面,並在瀏覽器認為頁面互動告一段落後自動停止 profiling。
因為我想要檢測的是使用者手動觸發的頁面動畫,所以使用第一種方式會比較適合。另外如果你的網站也有手機版的需求,一般來說有些 Mobile Devices 的性能會比較差,為了檢測應用在 low-level 的 Mobile Devices 是否也能夠流暢的運作,可以選擇 CPU 選項(圖中橘色區塊)並調整 CPU 的性能,這邊我選擇 6x slower 來模擬在性能低的設備運作的狀況。點選開始檢測後,操作一下想要檢測的功能,點選 stop profiling,一段時間後 Devtool 就會呈現 Profiling 的結果。
讓我們一步一步拆解,首先來看看紀錄 FPS、CPU、NET 的圖表區域。
提到測量網頁動畫的效能,最直覺的方式就是觀察 FPS (Frame Per Second),也就是常常聽到的 Frame Rate。當在進行動畫時,會希望 Frame Rate 可以達到 60 FPS 左右,使用者看到的動畫才不易產生卡頓。(有時候 FPS 很低也不一定不好,也有可能是頁面真的沒有任何的動靜)
在上圖的 FPS 欄位可以看到粉紅色與紅色組成的 bar,這代表這頁面可能會有掉幀的狀況,這會導致頁面動畫不平順甚至卡頓,嚴重影響使用者體驗,是我們應該要盡量避免的。
從上圖可以看到 CPU 圖表是有時段性的,有些區段看起來十分活躍,有先區段看起來卻幾乎沒有在運作。滑鼠在 FPS、CPU、NET 上 hover 都可以看到 profiling 過程完整的 Screenshot,這種技術也叫做 scrubbing,可以讓我們以漸進式的方式追蹤頁面動畫。
從上面的 Screenshot 可以得知在使用者點擊重新排列觸發動畫一直到動畫完成之間的時間都會讓 CPU 的使用量提高。
而 CPU 圖表有分很多不同的顏色,這代表著不同種類的工作,如果想要更容易懂且統整指定時間區段的圖表,可以在 Performance Tab 底下 Summary 看到統整後的圖表,顏色的對應與上面的 CPU section 是一樣的。
如果你發現你的網頁的 CPU 長時間都是保持 maxed out (圖表中看起來很繁忙很多工作要做) 的狀態,就要注意一下是不是有機會透過 Code Refactor 或是拔除不必要的功能來減輕 CPU 的 Workload。
當然不代表 CPU 長時間都在運作就是不好的,舉例來說 asana 的官網有一些會無限輪迴的動畫
這樣 CPU 勢必會得一直處理一些工作,如果需求是這樣,也未必不好
不過可以看得出來掉幀的狀況蠻嚴重的,還有 JavaScript 的執行佔了 CPU 的多數時間,這時候可以更近一步去思考如何改善掉幀的問題,或是動畫能不能盡量用 CSS 就達到一樣的效果(原因在於 CSS 架構的動畫通常是由瀏覽器「主執行緒」的另外一個獨立的執行緒處理,詳情可以參考這篇文章)。
接下來往下看到 Performance Tab 中間區段的更多詳細資訊部分。因為自己的 demo site 在這邊顯示的資訊太少,所以選擇使用剛剛提到的 asana 官網來解釋這個區塊,而礙於篇幅我只會提到幾個我認為比較常會用到的指標 — Memory、Frames、Timing、Main、Network、Experience。
點選紅色區塊的 Memory 選項,下方便會顯示這段 profiling 期間網頁的記憶體用量,例如說觀測藍色 JS Heap 使用量的變化我們大致可以觀察出網頁有沒有 Memory Leak 的問題,也可以得知大概是哪些操作會導致記憶體用量飆升。
橘色區塊的垃圾桶則是可以強制瀏覽器做 GC (Garbage Collection),因為 GC 在 JavaScript 裡是不可控的,所以很難只看程式碼就找出可能產生 Memory Leak 的狀況。藉由強制 GC,我們可以觀測出執行一個函式前後的記憶體用量差別。例如在執行某個函式後就強制 GC,如果記憶體使用量還是在高點甚至越來越高,也許就是遇到 Memory Leak 的狀況了。
記憶體回不來,看來是遇到 Memory Leak 了QQ
如果觀察到 GC 是高頻率被觸發的也不要高興得太早,雖然可能可以解決 Memory Leak 的問題,但也代表著網站記憶體的用量有點太多了,GC 有一個特性是「Stop The World」,因為 GC 同樣是在 Main Thread 執行,太過頻繁可能會導致網站效能出現瓶頸。若對前端記憶體管理機制不熟悉的朋友可以參考我之前的文章 — 身為 JS 開發者,你應該要知道的記憶體管理機制。
中間區塊的 Frames Section 顯示了頁面中每一次 UI update 的 Screenshot,而每一次 UI update 又可以被稱作一個 「frame」。當 UI 長時間被卡住無法更新時就稱作是一個「 long frame」。
點擊 Frames 中顯示的其中一個 frame,下方的 summary tab 會顯示該 frame 的詳細資訊,包括 Duration、FPS 與 CPU Time。
如果點擊 summary 中的 Screenshot 還能一步步依照時間順序瀏覽被捕捉到的 frames。
Timing Section 可以看到網頁載入效能一些重要指標發生的時間線,這些指標分別有:
在 Performance Tab 的最下方也會顯示 Total Blocking Time (TBT),代表在 Profiling 的過程中,瀏覽器 Main Thread 被阻塞的時間總和,當然我們會希望這個時間可以越短越好。
從 Experience 可以看出哪裡會發生 Layout Shift,也就是 Core Web Vital 中大家十分在意的 CLS。(如果不知道 Core Web Vital 是什麼的讀者可以參考我在鐵人賽的這篇文章)
按照順序顯示抓取各個資源花費的時間和資源間的依賴關係,如果點擊任一資源可以在下方 summary tab 看到更詳細的資料,例如 URL、Duration、Priority、Mime Type、Encoded Data 大小、Decoded Body 大小。
不過筆者通常在 debug networking 相關的資源時還是比較常直接使用 Devtool 的 Network Tab,整體資訊更為詳細且直覺。
Main Section 呈現的是 Main Thread 的 CPU task,並且採用倒轉的火焰圖(flame chart)的形式呈現,意思是在下面的 function 或 task 是由上一層的 task 所觸發的,我們可以利用這點追蹤各個 task 與 function 的依賴與觸發關係。
如果 Task 的右上角出現紅色的三角形,代表這是一個 long task,同時這也是一個告訴我們這裡可能有出現一些問題導致 task 執行時間過長的警告。
點選有紅色三角形的 task 後,會出現如上圖的詳細資訊,有時候它會指出這個 task 在程式碼中的位置,讓我們可以更快速的 debug 找出潛在的問題,例如我們點擊上面顯示的 content.js
會直接切換到 Sources Tab 並顯示對應的行數,對於 Debug 來說非常方便。
有時候你會發現除了 Main,還多了其他的 Frame 與 Worker ,這些 Frame 指的不是剛剛提到可以觀測 FPS 的 Frames,而是指網頁中有嵌入的 iframe,而 Worker 指的是 Web Workers 等除了 Main Thread 以外的執行緒,Performance Tab 連這些都可以 debug,真的非常令人驚艷。
礙於篇幅,其實 Performance Tab 上還有很多豐富的資訊沒有介紹到,例如關於 Layer 相關的資訊、Advanced Paint Instrumentation,還有 Summary Tab 底下關於 Call Tree、Bottom-Up 等可以更細粒度的觀察 CPU 活動的區塊,蠻建議有興趣與有需要的讀者可以自行研究一下。
理解了 Performance Tab 基本的操作方式後,回過頭來看看用來 Demo 的網站在 Performance Tab 的分析狀況。
可以看出在動畫觸發時基本上會嚴重掉幀, CPU 的工作量也會變得很大,主要在做的事是 Rendering(紫色)與 Painting(綠色),Idle(白色)的時間並不多,有可能會導致沒辦法處理使用者的 input 行為。
按照剛剛介紹的方式,也可以進一步去看在 Main Section 標示為 long task 的任務,發現大部分的時間都花在 painting 上了,且花費的時間非常久,依照 RAIL Model 的標準,這樣的時間花費會讓使用者感受到頁面的卡頓,甚至使用者互動所觸發的事件瀏覽器也會沒辦法及時處理,使用者體驗非常不好。
到這裡大致上可以確定這個版本的動畫有很大的問題,既然確定了問題,我們就來看看動畫的 code
發現這段 code 是直接改變 element 的 top 來達成動畫,這會造成頁面每次都需要經過 Reflow 重新計算 Layout。但我們應該有更好的方式可以達成一樣的動畫效果的,於是我試著將 code 改成以下這樣
並且使用 will-change CSS 屬性在 transform 上
如果不知道為什麼這樣改可以改善動畫效能的讀者建議先閱讀以下兩篇文章:
修改後的網站為:
接著我們直接對新版的網站進行 Performance Profiling
可以發現 frame rate 與 CPU 使用量都優化非常多,從 summary tab 也可以發現同樣是差不多一秒左右的時間,新版網頁的 Idle Time 變多了,代表更有機會可以處理使用者的 input,避免使用者感受到整個網站是失去響應的。
雖然改版後還是有些失幀的狀況,應該還能再更加改進,但比起上一版已經改善非常多。
透過這個簡單的範例,各位讀者未來在遇到頁面不流暢或是卡頓等問題時應該就比較知道怎麼 debug ,在修正寫法後也知道如何比對是不是真的有改善。
Demo Source Code :https://github.com/kylemocode/it-ironman-2021/tree/master/css-transform-demo?source=post_page-----4f0efd27b86d--------------------------------
現在的網站十分依賴與看重像 Lighthouse 這種效能檢測工具所算出的分數,但筆者就曾經遇過明明 Lighthouse Performance 分數是滿分,在使用時卻有明顯卡頓與不流暢的狀況。這代表我們不能完全相信與依賴這些分數,當使用者親身遇到效能體驗不佳的狀況時,Runtime Performance 的 Debugging 就變得十分重要。雖然今天只有介紹到皮毛,但希望這篇文章能夠讓各位在未來需要使用 Performance Tab 時不再被眼花繚亂的 Dashboard 震懾住,而可以清楚地知道要如何去找出問題的瓶頸。
文章最後推薦一位大大撰寫的關於前端 Debug 的著作,內容真的十分精實與受用,推薦給大家。