使用 Performance 看看瀏覽器在作什麼

前言

Chrome 瀏覽器的 Performance 面板爲咱們提供了檢測頁面性能的能力,但其提供的遠不止一些性能數據。本文將從工做原理的視角,結合實際工程的錄製結果,探一探性能面板向咱們透露的其餘信息。javascript

性能面板

關於面板的功能與使用方法,能夠參考這篇 文章。本節主要介紹瀏覽器架構與性能面板的關係。

由於還沒有決出最終的標準架構,各大瀏覽器的實現細節各有不一樣。這裏咱們以 Chrome 的架構爲例,對照其架構與性能面板的關係。
image.pngimage.png
由下圖咱們能夠看到性能面板呈現的幾個主要線程。性能面板並不包含架構中所有的線程,主要仍是與頁面渲染過程相關的部分。
image.pnghtml

Network

Network 表明瀏覽器進程中的網絡線程,咱們能夠看到時間軸上包含了全部的網絡請求和文件下載的調用信息,並以不一樣顏色標識不一樣類型的資源。
image.pngjava

Main

Main 表明渲染進程中的主線程,渲染相關的事情基本都是它來作,腳本執行、樣式計算、佈局計算、繪製等等。
image.pnggit

Compositor & Raster

Compositor 表明渲染進程中的合成線程,Raster 表明渲染進程中的柵格線程。現在瀏覽器繪製一個頁面,能夠分爲如下幾步:github

  • 主線程將頁面分紅若干圖層(後文中會說起 Update Layer Tree)
  • 柵格線程分別對每個層進行柵格化處理
  • 合成線程將柵格化的圖塊合併成一個頁面

image.png
咱們能夠看到,在性能面板中主線程在最後調用了柵格線程作實際的渲染。image.pngweb

GPU

顯然,這部分就是 GPU Process 中的 GPU 線程。chrome

瀏覽器的工做報告

接下來咱們將大體從時間維度,看看瀏覽器錄製下來的「工做報告」。api

文檔的下載解析

咱們旅途的起點將從點擊 Chrome Performance Panel 的 Reload 按鈕(形如刷新)開始。當前頁面首先進行卸載,伴隨着幾個日誌上報,瀏覽器開始了 index.html 的下載工做。
image.png
HTML 文檔下載完成後,瀏覽器開始按照 HTML 標準對 index.html 進行解析,在主線程中將接收到的文本字符串解析爲 DOM 。咱們能夠注意到,HTML 的解析過程並非一鼓作氣,這是由於 HTML 一般還包括了其餘外部資源,如圖片、CSS、JS 等。這些文件須要經過網絡請求或緩存來獲取。其中,當 HTML 解析器解析到 <script> 標籤時,HTML 文檔的解析過程就會停止,轉而去加載、解析和執行腳本。所以,從主線程的時間軸能夠看出,Parse HTML 的過程是斷斷續續的。
image.png瀏覽器

不一樣資源的處理

如下處理策略均可以在主線程中看到,可是不一樣資源的處理條長短差距較大,截圖困難,這裏不作呈現。

那麼瀏覽器對不一樣資源的處理策略是怎樣的呢?緩存

  • 瀏覽器下載 HTML 並解析,若是遇到外部 CSS 等資源,就會由 Browser 進程中的 network 線程下載
  • 當 CSS 下載時,HTML 的解析過程能夠繼續
  • 當解析遇到了外部 Script 標籤(不包含 async、defer 屬性)時,解析中止,直到腳本下載並執行完成

總的來講,瀏覽器對 HTML 的解析過程不會被 CSS、IMG 等資源的下載阻塞,但腳本的加載和執行會終止 HTML 的解析。這主要是由於 JS 可能會改變 DOM 的結構,或者是 JS 動態加載其餘 JS 再改變 DOM 等潛在問題。

顯然,儘管瀏覽器能夠併發幾個 network 線程下載資源,但若是僅像上述策略這樣處理,當解析到 <script> 時,若是文件較大或者延遲較高,可能會發生「腳本獨佔線程而沒有其餘資源在下載」的空窗期(idle network)。所以,pre-loader (或者 preload scanner 等叫法)將會在主線程以外,掃描餘下的標籤,充分利用 network 線程下載其餘資源。這種機制能夠優化 19% 的加載時長。

腳本的解析執行

對於重業務邏輯的複雜中後臺應用而言,腳本帶來的性能開銷,每每是佔主要地位的。咱們從下圖的例子就能夠看出,去除 beforeunload 以前的卸載,腳本自己的時間開銷佔比已過半。解析 HTML 在其次,至於其餘樣式計算、微任務、垃圾回收等等,倒不是最痛的地方。固然,該例子工程自己重業務邏輯,JavaScript 代碼量決定着其高成本。
image.png
有時咱們能夠考慮使用 async 或者 defer 屬性來提升頁面性能,兩者的差別再也不贅述。須要專門說明的是動態添加腳本的狀況。以下面示例代碼所示,腳本被 append 到文檔中後就會開始下載,而且默認和 async 具備同樣的行爲,即「先加載完的先執行」。

let script = document.createElement('script');
script.src = "/xxx/a.js";
document.body.append(script);

若是專門設置了 async 屬性,則會按照 defer 的行爲來,即「先加載到的先執行」。

function loadScript(src) {
  let script = document.createElement('script');
  script.src = src;
  script.async = false;
  document.body.append(script);
}

// 由於 async = false,因此按順序先執行 big;不然(通常會先)執行 small
loadScript("/xxx/big.js");
loadScript("/xxx/small.js");

從下圖中能夠看到,調用棧中執行的 appendChild 方法動態添加了 script 腳本,以後很快開始了下載動做。動態加載的腳本完成下載後,又第一時間開始了腳本執行。
image.png
image.png

lifecycle 和 paint timing

下圖展現的是文章中說起的頁面生命週期流程圖。本節咱們結合 Performance,對照該圖進行觀察。
image.png

beforeunload

由於 Performance 的錄製是在已有頁面上進行 reload,因此記錄的生命週期從頁面的卸載開始。以下圖 Main 所示,beforeunload 事件首先被瀏覽器觸發。能夠注意到,黃色條 Event: beforeunload 是瀏覽器自身觸發的活動,咱們稱之爲根活動(Root activities)
image.png

pagehide

從下圖中咱們能夠注意到,爲何事件的觸發順序和上面的生命週期流程圖不一致,是 pagehide -> visibilitychange -> unload 呢?事實上,在瀏覽器以前的設計中,若是頁面在卸載階段可視,visibilitychange 就會在 pagehide 以後觸發,正以下圖截圖中同樣。這就使得頁面的卸載在不一樣可視狀況下,有着不一致的生命週期與事件順序,給開發者帶來複雜性。
image.png
在將來新版本瀏覽器中,卸載階段的事件順序會進行統一,目前進度在這一 issue 下。也正由於這部分的調整,unload 已經不建議在代碼實現中使用了。

first paint

首先區分下如下兩個時間點:

  • first paint:指的是首個像素開始繪製到屏幕上的時機,例如一個頁面的背景色
  • first contentful paint:指的是開始繪製內容的時機,如文字或圖片

image.png

從 Performance 中,咱們能夠看出首次繪製的一系列動做(有些過程啪的一下很快啊,截圖就省了):

  1. CSS 加載完成
  2. Parse Stylesheet:解析樣式表,構建出 CSSOM
  3. Recalculate Style:從新計算樣式,肯定樣式規則
  4. Layout:根據計算結果進行佈局,肯定元素的大小和位置
  5. Update Layer Tree:更新渲染層樹
  6. Paint:根據 Layer Tree 繪製頁面(位置、大小、顏色、邊框、陰影等)
  7. Composite Layers:組合層,瀏覽器將圖層合併後輸出到屏幕

image.png

Layout 以後的過程很快,這裏放大些倍數來查看:
image.png

DOMContentLoaded

DOMContentLoaded 表示 HTML 已經徹底被加載和解析,固然樣式表、圖片等資源還不必定已經完成加載。從下圖中能夠看到,通過多段 HTML 解析後,DCL 以後就沒有其餘的 Parse HTML 了。
image.png

pageshow/load

因導航而使得瀏覽器在窗口內呈現文檔時,瀏覽器會在 window 上觸發 pageshow 事件,具體的時機可參考這裏。不只如此,當頁面是初次加載時,pageshow 事件會在 load 事件後觸發。

那麼回到 Performance 的時間軸,從下圖咱們能夠看到,在紅色虛線(標誌着 load)以後,瀏覽器觸發了 pageshow 事件,也就是上文說起的根活動。
image.png

任務與性能問題

比較惋惜的是,Performance 還沒法清晰的看出 Event Loop。下圖中灰色的 Task 並非指宏任務,其表明的是「當前主線程忙碌,沒法響應用戶交互」;Run Microtasks 則確實是在一次任務的末尾執行的微任務。當咱們點開調用棧觀察時,能夠看到源碼中的回調函數以及對應的源碼位置。
image.png
經過 Task 能夠定位性能出現問題的地方。RAIL 模型告訴咱們須要重點關注佔用 CPU 超出 50ms 的複雜任務,以提供連貫的交互體驗。固然,這裏更多的是對交互階段的響應的要求,而不必定是對初始加載階段的要求。
image.png

總結

本文從工做原理的視角,結合實際工程的錄製結果,進行了一次實踐對理論知識的檢驗。Performance 不只是性能分析工具,仍是探究瀏覽器工做原理的小霸王學習機。總的來講,瀏覽器的工做是充實且複雜的,與咱們打工人的摸魚平常造成了對比,仍是須要進一步加深學習與思考呀。

參考連接

[1] Measure performance with the RAIL model
[2] Get Started With Analyzing Runtime Performance
[3] Inside look at modern web browser
[4] JavaScript Start-up Performance
[5] How browsers work
[6] How the Browser Pre-loader Makes Pages Load Faster

文章可隨意轉載,但請保留此原文連接。
很是歡迎有激情的你加入 ES2049 Studio,簡歷請發送至 caijun.hcj@alibaba-inc.com 。
相關文章
相關標籤/搜索