Leistung kritischer Rendering-Pfade analysieren

Ilya Grigorik
Ilya Grigorik

Veröffentlicht: 31. März 2014

Um Leistungsengpässe im kritischen Pfad des Renderings zu identifizieren und zu beheben, müssen Sie die häufigsten Fallstricke kennen. Eine Demo zur Identifizierung häufiger Leistungsmuster hilft Ihnen, Ihre Seiten zu optimieren.

Durch die Optimierung des kritischen Rendering-Pfads kann der Browser die Seite so schnell wie möglich anzeigen. Schnellere Seiten führen zu mehr Interaktionen, mehr aufgerufenen Seiten und höheren Conversions. Um die Zeit zu minimieren, die ein Besucher vor einem leeren Bildschirm verbringt, müssen wir optimieren, welche Ressourcen geladen werden und in welcher Reihenfolge.

Um diesen Prozess zu veranschaulichen, beginnen Sie mit dem einfachsten Fall und erweitern Sie unsere Seite schrittweise um zusätzliche Ressourcen, Stile und Anwendungslogik. Dabei optimieren wir jeden Fall und sehen uns auch an, wo es zu Problemen kommen kann.

Bisher haben wir uns ausschließlich darauf konzentriert, was im Browser passiert, nachdem die Ressource (CSS-, JS- oder HTML-Datei) zur Verarbeitung verfügbar ist. Die Zeit, die zum Abrufen der Ressource aus dem Cache oder aus dem Netzwerk benötigt wird, wurde ignoriert. Wir gehen davon aus, dass Folgendes zutrifft:

  • Ein Netzwerk-Umlauf (Weitergabelatenz) zum Server kostet 100 ms.
  • Die Serverantwortzeit beträgt 100 ms für das HTML-Dokument und 10 ms für alle anderen Dateien.

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>

Ausprobieren

Beginnen Sie mit einfachem HTML-Markup und einem einzelnen Bild. Verwenden Sie kein CSS oder JavaScript. Öffnen Sie dann in den Chrome-Entwicklertools den Bereich „Netzwerk“ und prüfen Sie die resultierende Ressourcenabfolge:

CRP

Wie erwartet dauerte der Download der HTML-Datei etwa 200 ms. Beachten Sie, dass der transparente Teil der blauen Linie die Zeit angibt, die der Browser im Netzwerk wartet, ohne Antwort-Byte zu empfangen, während der durchgezogene Teil die Zeit anzeigt, die für den Abschluss des Downloads nach Erhalt der ersten Antwort-Byte benötigt wird. Der HTML-Download ist sehr klein (< 4K), sodass wir nur einen einzigen Roundtrip benötigen, um die vollständige Datei abzurufen. Das Abrufen des HTML-Dokuments dauert daher etwa 200 ms, wobei die Hälfte der Zeit auf das Netzwerk und die andere Hälfte auf die Serverantwort wartet.

Sobald die HTML-Inhalte verfügbar sind, parset der Browser die Bytes, wandelt sie in Tokens um und erstellt den DOM-Baum. In den DevTools wird unten die Zeit für das DOMContentLoaded-Ereignis (216 ms) angezeigt, was auch der blauen vertikalen Linie entspricht. Die Lücke zwischen dem Ende des HTML-Downloads und der blauen vertikalen Linie (DOMContentLoaded) ist die Zeit, die der Browser benötigt, um die DOM-Baumstruktur zu erstellen – in diesem Fall nur wenige Millisekunden.

Das Ereignis domContentLoaded wurde durch unser „tolles Foto“ nicht blockiert. Wie sich herausstellt, können wir die Rendering-Struktur erstellen und sogar die Seite zeichnen, ohne auf jedes Asset auf der Seite warten zu müssen: Nicht alle Ressourcen sind entscheidend, um den schnellen First Paint zu liefern. Wenn wir vom kritischen Rendering-Pfad sprechen, meinen wir in der Regel das HTML-Markup, CSS und JavaScript. Das anfängliche Rendern der Seite wird durch Bilder nicht blockiert. Wir sollten aber auch versuchen, die Bilder so schnell wie möglich zu erstellen.

Das load-Ereignis (auch onload genannt) wird jedoch im Image blockiert: Die Entwicklertools melden das onload-Ereignis nach 335 ms. Das Ereignis onload kennzeichnet den Punkt, an dem alle für die Seite erforderlichen Ressourcen heruntergeladen und verarbeitet wurden. An diesem Punkt kann das Ladesymbol im Browser (die rote vertikale Linie in der Ablaufgrafik) aufhören zu rotieren.

JavaScript und CSS hinzufügen

Die Seite „Hello World“ mag einfach erscheinen, aber im Hintergrund passiert eine Menge. In der Praxis benötigen wir jedoch mehr als nur HTML: Es ist wahrscheinlich, dass wir ein CSS-Stylesheet und ein oder mehrere Scripts haben, um unserer Seite Interaktivität zu verleihen. Probieren Sie beides aus, um zu sehen, was passiert:

<!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>

Ausprobieren

Vor dem Hinzufügen von JavaScript und CSS:

DOM-CRP

Mit JavaScript und CSS:

DOM, CSSOM, JS

Durch das Hinzufügen externer CSS- und JavaScript-Dateien werden der Vermittlungsabfolge zwei zusätzliche Anfragen hinzugefügt, die der Browser alle etwa gleichzeitig sendet. Beachten Sie jedoch, dass der Zeitunterschied zwischen den Ereignissen domContentLoaded und onload jetzt viel geringer ist.

Was ist passiert?

  • Im Gegensatz zu unserem einfachen HTML-Beispiel müssen wir auch die CSS-Datei abrufen und parsen, um das CSSOM zu erstellen. Außerdem benötigen wir sowohl das DOM als auch das CSSOM, um den Renderbaum zu erstellen.
  • Da die Seite auch eine JavaScript-Datei enthält, die den Parser blockiert, wird das domContentLoaded-Ereignis blockiert, bis die CSS-Datei heruntergeladen und geparst wurde. Da das JavaScript das CSSOM abfragen könnte, müssen wir die CSS-Datei blockieren, bis sie heruntergeladen wurde, bevor wir JavaScript ausführen können.

Was passiert, wenn wir unser externes Script durch ein Inline-Script ersetzen? Auch wenn das Script direkt in die Seite eingebettet ist, kann es erst ausgeführt werden, wenn das CSSOM erstellt wurde. Kurz gesagt: Eingebettetes JavaScript wird ebenfalls vom Parser blockiert.

Wird die Seite durch das Einfügen des Scripts trotz CSS-Blockierung schneller gerendert? Probieren Sie es aus und sehen Sie, was passiert.

Externes JavaScript:

DOM, CSSOM, JS

Inline-JavaScript:

DOM, CSSOM und Inline-JS

Wir stellen eine Anfrage weniger, aber sowohl die onload- als auch die domContentLoaded-Zeiten sind praktisch gleich. Warum? Wir wissen, dass es keine Rolle spielt, ob der JavaScript-Code inline oder extern ist, denn sobald der Browser das Skript-Tag erreicht, blockiert er und wartet, bis das CSSOM erstellt ist. Außerdem lädt der Browser in unserem ersten Beispiel sowohl CSS als auch JavaScript parallel herunter und der Download ist ungefähr gleichzeitig abgeschlossen. In diesem Fall nützt uns das Inlinen des JavaScript-Codes nicht viel. Es gibt jedoch mehrere Strategien, mit denen sich das Rendern der Seite beschleunigen lässt.

Wie Sie wissen, werden alle Inline-Scripts vom Parser blockiert. Externen Scripts können wir jedoch das async-Attribut hinzufügen, um den Parser zu entsperren. Heben Sie das Inline-Einfügen auf und versuchen Sie es noch einmal:

<!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>

Ausprobieren

Parser-blockierendes (externes) JavaScript:

DOM, CSSOM, JS

Asynchrones (externes) JavaScript:

DOM, CSSOM, asynchrones JS

Viel besser! Das Ereignis domContentLoaded wird kurz nach dem Parsen der HTML-Datei ausgelöst. Der Browser weiß, dass JavaScript nicht blockiert werden darf. Da es keine anderen Scripts gibt, die den Parser blockieren, kann die CSSOM-Konstruktion auch parallel erfolgen.

Alternativ hätten wir sowohl das CSS als auch das JavaScript einfügen können:

<!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>

Ausprobieren

DOM, Inline-CSS, Inline-JS

Beachten Sie, dass die domContentLoaded-Zeit im Grunde dieselbe ist wie im vorherigen Beispiel. Anstatt unser JavaScript als asynchron zu kennzeichnen, haben wir sowohl das CSS als auch das JS in die Seite selbst eingebettet. Dadurch wird unsere HTML-Seite viel größer, aber der Vorteil ist, dass der Browser nicht warten muss, um externe Ressourcen abzurufen. Alles ist direkt auf der Seite.

Wie Sie sehen können, ist das Optimieren des kritischen Rendering-Pfads selbst bei einer sehr einfachen Seite eine nicht triviale Aufgabe: Wir müssen das Abhängigkeitsdiagramm zwischen verschiedenen Ressourcen verstehen, ermitteln, welche Ressourcen „kritisch“ sind, und eine Strategie auswählen, um diese Ressourcen in die Seite einzubinden. Es gibt keine allgemeingültige Lösung für dieses Problem, da jede Seite anders ist. Sie müssen einen ähnlichen Prozess für sich selbst durchlaufen, um die optimale Strategie zu ermitteln.

Sehen wir uns an, ob wir einige allgemeine Leistungsmuster identifizieren können.

Leistungsmuster

Die einfachste Seite besteht nur aus dem HTML-Markup, ohne CSS, JavaScript oder andere Arten von Ressourcen. Um diese Seite zu rendern, muss der Browser die Anfrage initiieren, auf das Eintreffen des HTML-Dokuments warten, es parsen, das DOM erstellen und es schließlich auf dem Bildschirm rendern:

<!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>

Ausprobieren

Hello World-CRP

Die Zeit zwischen T0 und T1 umfasst die Netzwerk- und Serververarbeitungszeiten. Im günstigsten Fall (wenn die HTML-Datei klein ist) ruft nur ein Netzwerk-Roundtrip das gesamte Dokument ab. Aufgrund der Funktionsweise der TCP-Transportprotokolle können bei größeren Dateien mehr Umläufe erforderlich sein. Im besten Fall hat die obige Seite also einen kritischen Renderingpfad mit nur einer Umdrehung.

Sehen wir uns nun dieselbe Seite mit einer externen CSS-Datei an:

<!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>

Ausprobieren

DOM + CSSOM CRP

Auch hier erfolgt ein Netzwerk-Roundtrip zum Abrufen des HTML-Dokuments. Das abgerufene Markup teilt uns dann mit, dass wir auch die CSS-Datei benötigen. Das bedeutet, dass der Browser zum Server zurückkehren und das CSS abrufen muss, bevor er die Seite auf dem Bildschirm rendern kann. Daher sind für diese Seite mindestens zwei Hin- und Rückwege erforderlich, bevor sie angezeigt werden kann. Auch hier kann die CSS-Datei mehrere Roundtrips erfordern, daher liegt der Schwerpunkt auf „Minimum“.

Im Folgenden finden Sie einige Begriffe, die wir für den kritischen Rendering-Pfad verwenden:

  • Kritische Ressource:Ressource, die das erste Rendern der Seite blockieren könnte.
  • Länge des kritischen Pfades: Anzahl der Roundtrips oder die Gesamtzeit, die zum Abrufen aller kritischen Ressourcen erforderlich ist.
  • Kritische Byte: Die Gesamtzahl der Byte, die für das erste Rendern der Seite erforderlich sind. Dies ist die Summe der Übertragungsdateigrößen aller kritischen Ressourcen. Unser erstes Beispiel mit einer einzelnen HTML-Seite enthielt eine einzige kritische Ressource (das HTML-Dokument). Die Länge des kritischen Pfads entsprach ebenfalls einem Netzwerk-Roundtrip (vorausgesetzt, die Datei war klein) und die Gesamtzahl der kritischen Bytes entsprach nur der Übertragungsgröße des HTML-Dokuments selbst.

Vergleichen Sie dies nun mit den Eigenschaften des kritischen Pfads aus dem vorherigen HTML- und CSS-Beispiel:

DOM + CSSOM CRP

  • 2 kritische Ressourcen
  • 2 oder mehr Umläufe für die minimale kritische Pfadlänge
  • 9 KB kritische Bytes

Wir benötigen sowohl HTML als auch CSS, um die Rendering-Struktur zu erstellen. Daher sind sowohl HTML als auch CSS kritische Ressourcen: Das CSS wird erst abgerufen, nachdem der Browser das HTML-Dokument erhalten hat. Daher beträgt die Länge des kritischen Pfads mindestens zwei Rundreisen. Beide Ressourcen zusammen ergeben insgesamt 9 KB kritische Byte.

Fügen Sie jetzt eine zusätzliche JavaScript-Datei hinzu.

<!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>

Ausprobieren

Wir haben app.js hinzugefügt. Das ist sowohl ein externes JavaScript-Asset auf der Seite als auch eine Parser-Blockierungsressource (also eine kritische) Ressource. Schlimmer noch: Um die JavaScript-Datei auszuführen, müssen wir das CSSOM blockieren und darauf warten. Denken Sie daran, dass JavaScript das CSSOM abfragen kann. Daher wird der Browser angehalten, bis style.css heruntergeladen und das CSSOM erstellt wurde.

DOM, CSSOM, JavaScript-CRP

Wenn wir uns jedoch die „Netzwerkabfolge“ dieser Seite ansehen, sehen wir, dass sowohl die CSS- als auch die JavaScript-Anfragen ungefähr gleichzeitig gestartet werden. Der Browser ruft die HTML-Datei ab, erkennt beide Ressourcen und startet beide Anfragen. Daher hat die im vorherigen Bild gezeigte Seite die folgenden Merkmale des kritischen Pfads:

  • 3 kritische Ressourcen
  • 2 oder mehr Umläufe für die minimale kritische Pfadlänge
  • 11 KB kritische Bytes

Wir haben jetzt drei kritische Ressourcen, die zusammen 11 KB kritische Bytes ergeben, aber unsere kritischen Pfadlänge beträgt immer noch zwei Roundtrips, da wir CSS und JavaScript parallel übertragen können. Wenn Sie die Eigenschaften Ihres kritischen Rendering-Pfads ermitteln möchten, müssen Sie die kritischen Ressourcen identifizieren und wissen, wie der Browser die Abrufe plant.

Nach einem Gespräch mit unseren Websiteentwicklern haben wir festgestellt, dass das JavaScript, das wir auf unserer Seite eingebunden haben, nicht blockiert werden muss. Es gibt einige Analyse- und anderen Code, der das Rendern unserer Seite nicht blockieren muss. Mit diesen Informationen können wir dem <script>-Element das async-Attribut hinzufügen, um den Parser zu entsperren:

<!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>

Ausprobieren

DOM, CSSOM, async JavaScript CRP

Ein asynchrones Skript hat mehrere Vorteile:

  • Das Script blockiert den Parser nicht mehr und ist nicht Teil des kritischen Rendering-Pfads.
  • Da es keine anderen kritischen Scripts gibt, muss das CSS das Ereignis domContentLoaded nicht blockieren.
  • Je früher das Ereignis domContentLoaded ausgelöst wird, desto früher kann die Ausführung anderer Anwendungslogik beginnen.

Daher umfasst unsere optimierte Seite nun wieder zwei kritische Ressourcen (HTML und CSS) mit einer Mindestlänge des kritischen Pfads von zwei Roundtrips und insgesamt 9 KB an kritischen Byte.

Wie würde das CSS-Stylesheet aussehen, wenn es nur für den Druck benötigt würde?

<!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>

Ausprobieren

DOM, nicht blockierender CSS-Code und asynchrone JavaScript-CRP

Da die style.css-Ressource nur für den Druck verwendet wird, muss der Browser sie nicht blockieren, um die Seite zu rendern. Sobald die DOM-Konstruktion abgeschlossen ist, verfügt der Browser über genügend Informationen, um die Seite zu rendern. Daher hat diese Seite nur eine einzige kritische Ressource (das HTML-Dokument) und die minimale Länge des kritischen Rendering-Pfads ist ein Hin- und Rücklauf.

Feedback