分析關鍵轉譯路徑效能

Ilya Grigorik
Ilya Grigorik

發布日期:2014 年 3 月 31 日

您必須充分瞭解常見的陷阱,才能找出並解決關鍵轉譯路徑效能瓶頸。您可以透過導覽,找出常見的成效模式,進而改善網頁。

只要最佳化關鍵轉譯路徑,瀏覽器就能盡可能快速繪製網頁:網頁載入速度越快,使用者參與度就越高,瀏覽的網頁數量也會增加,轉換率也會提升。為了盡可能縮短訪客觀看空白畫面的時間,我們需要最佳化載入的資源和載入順序。

為了說明這個程序,我們將從最簡單的情況開始,逐步建構頁面,加入其他資源、樣式和應用程式邏輯。在這個過程中,我們會針對每個案例進行最佳化調整,並找出可能發生錯誤的地方。

到目前為止,我們只專注於在資源 (CSS、JS 或 HTML 檔案) 可供處理後,瀏覽器會發生的情況。我們忽略從快取或網路擷取資源所需的時間。我們假設以下情況:

  • 網路封包往返伺服器的時間 (傳播延遲) 為 100 毫秒。
  • HTML 文件的伺服器回應時間為 100 毫秒,所有其他檔案的回應時間為 10 毫秒。

Hello World 體驗

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <title>Critical Path: No Style</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
  </body>
</html>

試用

請先使用基本的 HTML 標記和單一圖片,不要使用 CSS 或 JavaScript。接著,請在 Chrome 開發人員工具中開啟「Network」面板,並檢查產生的資源瀑布流:

CRP

如預期,HTML 檔案的下載時間約為 200 毫秒。請注意,藍線的透明部分代表瀏覽器在網路上等待的時間長度,且不會收到任何回應位元組,而實心部分則顯示收到第一個回應位元組後,完成下載的時間。HTML 下載內容很小 (小於 4K),因此我們只需要單一往返傳輸即可擷取完整檔案。因此,HTML 文件的擷取時間約為 200 毫秒,其中有一半的時間用於等待網路,另一半則用於等待伺服器回應。

當 HTML 內容可供使用時,瀏覽器會剖析位元組、將位元組轉換為符記,並建構 DOM 樹狀結構。請注意,DevTools 會在底部方便地回報 DOMContentLoaded 事件的時間 (216 毫秒),這也對應到藍色垂直線。HTML 下載結束和藍色垂直線 (DOMContentLoaded) 之間的間隔,是瀏覽器建構 DOM 樹狀結構所需的時間,在本例中僅需幾毫秒。

請注意,「超棒相片」並未封鎖 domContentLoaded 事件。結果證明,我們可以建構轉譯樹狀結構,甚至無須等待網頁上的每個素材資源,即可繪製網頁:並非所有資源都必須在首次顯示畫面時呈現。事實上,當我們談論關鍵運算路徑時,通常是指 HTML 標記、CSS 和 JavaScript。圖片不會阻礙網頁的初始轉譯作業,但我們也應盡快將圖片繪製完成。

不過,load 事件 (也稱為 onload) 在圖片上遭到封鎖:開發人員工具會在 335 毫秒時回報 onload 事件。請回想一下,onload 事件標示網頁所需的所有資源已下載及處理的時間點;此時,載入旋轉圖示可停止在瀏覽器中旋轉 (圖表中的紅色垂直線)。

將 JavaScript 和 CSS 加入混合

我們的「Hello World 體驗」頁面看似簡單,但實際上有許多幕後作業。實際上,我們需要的不僅是 HTML:我們很可能會使用 CSS 樣式表單和一或多個指令碼,為網頁增添互動功能。將兩者加入混合,看看會發生什麼事:

<!DOCTYPE html>
<html>
  <head>
    <title>Critical Path: Measure Script</title>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
  </head>
  <body onload="measureCRP()">
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script src="timing.js"></script>
  </body>
</html>

試用

新增 JavaScript 和 CSS 前:

DOM CRP

使用 JavaScript 和 CSS:

DOM、CSSOM、JS

加入外部 CSS 和 JavaScript 檔案會在我們的階層中新增兩個額外要求,瀏覽器會大致同時調度這兩個要求。不過,請注意,domContentLoadedonload 事件之間的時間差異現在已縮小許多。

為什麼會這樣?

  • 與純 HTML 範例不同,我們也需要擷取及剖析 CSS 檔案來建構 CSSOM,而且還需要 DOM 和 CSSOM 來建構轉譯樹狀結構。
  • 由於網頁也包含解析器封鎖 JavaScript 檔案,因此在 CSS 檔案下載及剖析前,domContentLoaded 事件會遭到封鎖:由於 JavaScript 可能會查詢 CSSOM,因此我們必須封鎖 CSS 檔案,直到下載完成後才能執行 JavaScript。

如果我們用內嵌指令碼取代外部指令碼,會怎麼樣?即使指令碼已直接內嵌至網頁,瀏覽器仍必須先建構 CSSOM 才能執行指令碼。簡單來說,內嵌 JavaScript 也是剖析器封鎖。

不過,即使 CSS 遭到封鎖,內嵌指令碼是否能讓網頁顯示速度加快?試試看會發生什麼事。

外部 JavaScript:

DOM、CSSOM、JS

內嵌 JavaScript:

DOM、CSSOM 和內嵌 JS

我們減少了一個要求,但 onloaddomContentLoaded 的時間實際上是相同的。這是因為我們知道,無論 JavaScript 是內嵌或外部,都不會造成影響,因為瀏覽器一遇到指令碼標記,就會封鎖並等待 CSSOM 建構完成。此外,在第一個範例中,瀏覽器會同時下載 CSS 和 JavaScript,並在幾乎同時完成下載。在這個例子中,內嵌 JavaScript 程式碼對我們幫助不大。不過,我們可以透過幾種策略加快網頁轉譯速度。

首先,請回想一下,所有內嵌指令碼都是解析器阻擋,但對於外部指令碼,我們可以新增 async 屬性來解除阻擋解析器。取消內嵌,然後試試看:

<!DOCTYPE html>
<html>
  <head>
    <title>Critical Path: Measure Async</title>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
  </head>
  <body onload="measureCRP()">
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script async src="timing.js"></script>
  </body>
</html>

試用

解析器阻斷 (外部) JavaScript:

DOM、CSSOM、JS

非同步 (外部) JavaScript:

DOM、CSSOM、非同步 JS

好多了!domContentLoaded 事件會在 HTML 剖析完成後立即觸發;瀏覽器知道不應封鎖 JavaScript,且由於沒有其他剖析器會封鎖指令碼,CSSOM 建構作業也可以同時進行。

或者,我們也可以將 CSS 和 JavaScript 內嵌:

<!DOCTYPE html>
<html>
  <head>
    <title>Critical Path: Measure Inlined</title>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <style>
      p {
        font-weight: bold;
      }
      span {
        color: red;
      }
      p span {
        display: none;
      }
      img {
        float: right;
      }
    </style>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script>
      var span = document.getElementsByTagName('span')[0];
      span.textContent = 'interactive'; // change DOM text content
      span.style.display = 'inline'; // change CSSOM property
      // create a new element, style it, and append it to the DOM
      var loadTime = document.createElement('div');
      loadTime.textContent = 'You loaded this page on: ' + new Date();
      loadTime.style.color = 'blue';
      document.body.appendChild(loadTime);
    </script>
  </body>
</html>

試用

DOM、內嵌 CSS、內嵌 JS

請注意,domContentLoaded 時間與前一個範例的時間相同;我們並未將 JavaScript 標示為非同步,而是將 CSS 和 JS 內嵌到網頁中。這會使 HTML 網頁變得更大,但好處是瀏覽器不必等待擷取任何外部資源,所有資源都會直接顯示在網頁中。

如您所見,即使是相當基本的網頁,要最佳化關鍵運算路徑也是一項不簡單的作業:我們需要瞭解不同資源之間的依附元件圖表,需要找出哪些資源是「關鍵」,並且必須從不同的策略中選擇如何在網頁上納入這些資源。這個問題沒有單一解決方案,每個網頁都不同。您必須自行按照類似的程序,找出最佳策略。

不過,我們可以先退一步,找出一些一般性的成效模式。

效能模式

最簡單的網頁只包含 HTML 標記,沒有 CSS、JavaScript 或其他類型的資源。如要算繪此頁面,瀏覽器必須發出要求、等待 HTML 文件傳送、剖析該文件、建構 DOM,然後在畫面上算繪:

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <title>Critical Path: No Style</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
  </body>
</html>

試用

Hello world CRP

T0 和 T1 之間的時間,可用來擷取網路和伺服器的處理時間。在最佳情況下 (如果 HTML 檔案很小),只需進行一次網路來回傳輸,即可擷取整份文件。由於 TCP 傳輸通訊協定運作方式的關係,較大的檔案可能需要更多往返次數。因此,在最佳情況下,上述網頁會有一個來回 (最少) 的關鍵轉譯路徑。

請再看看同一個網頁,但使用外部 CSS 檔案:

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
  </body>
</html>

試用

DOM + CSSOM CRP

再次執行網路來回作業以擷取 HTML 文件,然後擷取的標記會告訴我們也需要 CSS 檔案;也就是說,瀏覽器必須先返回伺服器並取得 CSS,才能在畫面上轉譯網頁。因此,這個網頁至少需要兩次往返才能顯示。再次強調,CSS 檔案可能需要多次來回傳輸,因此強調「最少」這個字眼。

以下是我們用來描述關鍵算繪路徑的幾個術語:

  • 重要資源:可能會阻礙網頁初始算繪作業的資源。
  • 關鍵路徑長度:來回傳輸次數,或擷取所有關鍵資源所需的總時間。
  • 重要位元組:取得網頁首個轉譯作業所需的總位元組數,也就是所有重要資源的傳輸檔案大小總和。在第一個範例中,單一 HTML 頁面包含單一重要資源 (HTML 文件);重要路徑長度也等於一次網路來回傳輸 (假設檔案很小),而總重要位元組數則是 HTML 文件本身的傳輸大小。

請比較這項特性與前述 HTML 和 CSS 範例的重要路徑特性:

DOM + CSSOM CRP

  • 2 重要資源
  • 2 以上來回傳輸,以達到最短關鍵路徑長度
  • 9 KB 的必要位元組

我們需要 HTML 和 CSS 來建構轉譯樹狀結構。因此,HTML 和 CSS 都是重要資源:瀏覽器會在取得 HTML 文件後才擷取 CSS,因此重要路徑長度至少為兩次往返。兩個資源加起來總共需要 9 KB 的必要位元組。

接著,請在混合中加入額外的 JavaScript 檔案。

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script src="app.js"></script>
  </body>
</html>

試用

我們新增了 app.js,這是網頁上的外部 JavaScript 素材資源,也是解析器封鎖 (也就是重要) 資源。更糟的是,為了執行 JavaScript 檔案,我們必須阻斷並等待 CSSOM;請注意,JavaScript 可以查詢 CSSOM,因此瀏覽器會暫停,直到 style.css 下載並建構 CSSOM 為止。

DOM、CSSOM、JavaScript CRP

不過,實際上,如果我們查看這個網頁的「網路階層」,就會發現 CSS 和 JavaScript 請求大約同時啟動;瀏覽器會取得 HTML、發現兩個資源,然後同時發出這兩項請求。因此,上圖所示的網頁具有下列關鍵路徑特性:

  • 3 項重要資源
  • 2 以上來回路徑,以達到最短關鍵路徑長度
  • 11 KB 的必要位元組

我們現在有三個重要資源,總共 11 KB 的必要位元組,但必要路徑長度仍為兩次往返,因為我們可以並行傳輸 CSS 和 JavaScript。找出關鍵運算路徑的特性,代表您能夠識別關鍵資源,並瞭解瀏覽器如何安排擷取作業。

與網站開發人員交談後,我們發現網頁中包含的 JavaScript 不需要封鎖;我們在其中加入了一些分析和其他程式碼,但這些程式碼不需要封鎖網頁的算繪作業。有了這些知識,我們就可以在 <script> 元素中加入 async 屬性,解除剖析工具的封鎖:

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script src="app.js" async></script>
  </body>
</html>

試用

DOM、CSSOM、非同步 JavaScript CRP

非同步指令碼有幾項優點:

  • 指令碼不再遭到剖析器封鎖,也不屬於關鍵轉譯路徑的一部分。
  • 由於沒有其他重要指令碼,CSS 就不需要封鎖 domContentLoaded 事件。
  • domContentLoaded 事件觸發得越早,其他應用程式邏輯就能越快開始執行。

因此,經過最佳化的網頁現在會回到兩個重要資源 (HTML 和 CSS),最小重要路徑長度為兩次往返,且總共 9 KB 的必要位元組。

最後,如果只需要用於列印的 CSS 樣式表單,會是什麼樣子?

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" media="print" />
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script src="app.js" async></script>
  </body>
</html>

試用

DOM、非阻斷 CSS 和非同步 JavaScript CRP

由於 style.css 資源只用於列印,因此瀏覽器不需要封鎖該資源即可轉譯網頁。因此,只要 DOM 建構完成,瀏覽器就會有足夠的資訊來轉譯網頁。因此,這個網頁只有一個必要資源 (HTML 文件),且必要運算路徑的長度最短為一輪。

意見回饋