Leistung kritischer Rendering-Pfade analysieren

Ilya Grigorik
Ilya Grigorik

Um Leistungsengpässe im kritischen Rendering-Pfad identifizieren und beheben zu können, müssen Sie sich mit den häufigen Fallstricken auskennen. Lassen Sie uns eine praktische Tour durchführen, bei der häufige Leistungsmuster extrahiert werden, die Ihnen bei der Optimierung Ihrer Seiten helfen.

Wenn Sie den kritischen Rendering-Pfad optimieren, kann der Browser die Seite so schnell wie möglich darstellen: Schnellere Seiten führen zu mehr Interaktionen, mehr Seitenzugriffen und einer besseren Conversion. Um die Zeit zu minimieren, die ein Besucher mit der Anzeige eines leeren Bildschirms verbringt, müssen wir optimieren, welche Ressourcen in welcher Reihenfolge geladen werden.

Zur Veranschaulichung dieses Prozesses beginnen wir mit dem einfachsten Fall und bauen unsere Seite schrittweise auf, um zusätzliche Ressourcen, Stile und Anwendungslogik aufzunehmen. Wir optimieren jeden Fall und sehen auch, wo Fehler auftreten können.

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 von Folgendem aus:

  • Ein Netzwerk-Roundtrip (Übertragungslatenz) 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-Erlebnis

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

Wir beginnen mit einfachem HTML-Markup und einem einzelnen Bild. CSS oder JavaScript sind nicht erforderlich. Sehen wir uns nun die Netzwerkzeitachse in den Chrome-Entwicklertools an und sehen uns die resultierende Ressourcenabfolge an:

CRP

Wie erwartet, dauerte der Download der HTML-Datei ungefähr 200 ms. Der transparente Teil der blauen Linie steht für die Zeit, die der Browser im Netzwerk wartet, ohne Antwortbyte zu erhalten. Der durchgehende Teil zeigt die Zeit bis zum Abschluss des Downloads nach Erhalt der ersten Antwortbyte. Der HTML-Download ist winzig (<4K), sodass wir nur einen einzigen Roundtrip brauchen, um die vollständige Datei abzurufen. Das bedeutet, dass das Abrufen des HTML-Dokuments etwa 200 ms dauert. Dabei wird die Hälfte der Zeit für das Warten auf das Netzwerk und die andere Hälfte auf die Serverantwort benötigt.

Wenn der HTML-Inhalt verfügbar ist, parst der Browser die Bytes, konvertiert sie in Tokens und erstellt den DOM-Baum. Die Entwicklertools melden bequem unten die Zeit für das DOMContentLoaded-Ereignis (216 ms), 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 den DOM-Baum zu erstellen – in diesem Fall nur wenige Millisekunden.

Wie Sie sehen, wurde die Veranstaltung „domContentLoaded“ nicht durch „tolles Foto“ blockiert. Wie sich herausstellt, können wir die Rendering-Baumstruktur erstellen und die Seite sogar malen, ohne auf jedes einzelne Asset auf der Seite warten zu müssen: Nicht alle Ressourcen sind entscheidend, um einen schnellen First Paint zu liefern. Wenn wir vom kritischen Rendering-Pfad sprechen, sprechen wir in der Regel von HTML-Markup, CSS und JavaScript. Bilder blockieren das anfängliche Rendern der Seite nicht. Wir sollten jedoch auch versuchen, die Bilder so schnell wie möglich darzustellen.

Allerdings wird das load-Ereignis (auch onload genannt) auf dem Bild blockiert: Die Entwicklertools melden das onload-Ereignis nach 335 ms. Mit dem onload-Ereignis wird der Punkt markiert, an dem alle Ressourcen, die für die Seite erforderlich sind, heruntergeladen und verarbeitet wurden. Nach diesem Zeitpunkt dreht sich das Ladesymbol nicht mehr im Browser (die rote vertikale Linie im Wasserfall).

JavaScript und CSS ergänzen

Unsere Seite „Hello World“ scheint einfach zu sein, aber im Hintergrund passiert viel. In der Praxis benötigen wir mehr als nur HTML: Wahrscheinlich haben wir ein CSS-Stylesheet und ein oder mehrere Skripts, um unserer Seite etwas Interaktivität zu verleihen. Fügen wir nun beide hinzu, 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 zwei zusätzliche Anfragen zu unserer Vermittlungsabfolge hinzugefügt. Diese werden alle ungefähr gleichzeitig vom Browser gesendet. Beachte jedoch, dass der Zeitunterschied zwischen den Ereignissen domContentLoaded und onload jetzt viel kleiner ist.

Woran liegt das?

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

Was passiert, wenn wir unser externes Skript durch ein Inline-Skript ersetzen? Selbst wenn das Skript direkt in die Seite eingefügt ist, kann der Browser es erst ausführen, wenn das CSSOM erstellt wurde. Kurz gesagt: Inline-JavaScript blockiert auch Parser.

Wird die Seite trotz der Blockierung in CSS schneller gerendert? Probieren wir es aus und schauen, was passiert.

Externes JavaScript:

DOM, CSSOM, JS

Inline-JavaScript:

DOM, CSSOM und Inline-JS

Wir senden eine Anfrage weniger, aber die Zeiten für onload und domContentLoaded sind praktisch identisch. Warum? Nun, wir wissen, dass es keine Rolle spielt, ob das JavaScript inline oder extern ist, denn sobald der Browser das Skript-Tag erreicht, wird er blockiert und wartet, bis das CSSOM erstellt ist. In unserem ersten Beispiel lädt der Browser sowohl CSS als auch JavaScript parallel herunter und der Download wird ungefähr zeitgleich abgeschlossen. In diesem Fall hilft uns die Inline-Anzeige des JavaScript-Codes nicht sehr. Es gibt jedoch einige Strategien, die dafür sorgen, dass unsere Seite schneller gerendert wird.

Beachten Sie zunächst, dass alle Inline-Skripts von Parsern blockiert werden. Für externe Skripts können wir jedoch das Keyword "async" hinzufügen, um die Blockierung des Parsers aufzuheben. Machen wir das Inline wieder rückgängig und probieren wir es aus:

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

Parserblockierendes (externes) JavaScript:

DOM, CSSOM, JS

Asynchrones (externes) JavaScript:

DOM, CSSOM, asynchrones JS

Das ist viel besser! Das domContentLoaded-Ereignis wird kurz nach dem Parsen des HTML-Codes ausgelöst. Der Browser weiß, dass er JavaScript nicht blockieren soll, und da keine anderen Parser-Blockierskripts vorhanden sind, kann die CSSOM-Erstellung parallel ausgeführt werden.

Alternativ können wir sowohl den CSS- als auch den JavaScript-Code inline einfügen:

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

Die domContentLoaded-Zeit ist praktisch dieselbe wie im vorherigen Beispiel. Statt unser JavaScript als asynchron zu kennzeichnen, haben wir sowohl CSS als auch JS in die Seite selbst eingefügt. Dadurch wird unsere HTML-Seite viel größer, der Vorteil ist jedoch, dass der Browser nicht auf den Abruf externer Ressourcen warten muss. Alles befindet sich direkt auf der Seite.

Wie Sie sehen, ist die Optimierung des kritischen Rendering-Pfads selbst bei einer sehr einfachen Seite eine nicht triviale Aufgabe: Wir müssen das Abhängigkeitsdiagramm zwischen verschiedenen Ressourcen verstehen, herausfinden, welche Ressourcen "kritisch" sind, und wir müssen zwischen verschiedenen Strategien wählen, wie diese Ressourcen in die Seite aufgenommen werden. Für dieses Problem gibt es keine einheitliche Lösung. Jede Seite ist anders. Sie müssen selbst einen ähnlichen Prozess durchlaufen, um die optimale Strategie zu ermitteln.

Versuchen wir dennoch, einige allgemeine Leistungsmuster zu identifizieren.

Leistungsmuster

Die einfachste mögliche Seite besteht nur aus HTML-Markup, also ohne CSS, JavaScript oder andere Arten von Ressourcen. Zum Rendern dieser Seite muss der Browser die Anfrage initiieren, auf den Eingang des HTML-Dokuments warten, es parsen, das DOM erstellen und 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

In der Zeit zwischen T0 und T1 werden die Verarbeitungszeiten von Netzwerk und Server erfasst. Im besten Fall (wenn die HTML-Datei klein ist) ruft nur ein Netzwerk-Roundtrip das gesamte Dokument ab. Aufgrund der Funktionsweise der TCP-Transportprotokolle benötigen größere Dateien möglicherweise mehr Roundtrips. Dadurch hat die obige Seite im besten Fall einen (minimalen) kritischen Rendering-Pfad.

Betrachten wir nun dieselbe Seite, jedoch mit einer externen CSS-Datei:

<!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 kommt ein Netzwerk-Roundtrip zum Abrufen des HTML-Dokuments vor. Das abgerufene Markup teilt uns dann mit, dass wir auch die CSS-Datei benötigen. Dies bedeutet, dass der Browser zum Server zurückkehren und die CSS abrufen muss, bevor er die Seite auf dem Bildschirm rendern kann. Deshalb sind für diese Seite mindestens zwei Roundtrips erforderlich, bevor sie angezeigt werden kann. Auch hier kann die CSS-Datei mehrere Umläufe durchlaufen, daher wird der Schwerpunkt auf „Minimum“ gesetzt.

Definieren wir das Vokabular, das wir zur Beschreibung des kritischen Rendering-Pfads verwenden:

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

Vergleichen wir das nun mit den Eigenschaften des kritischen Pfads im HTML- und CSS-Beispiel oben:

DOM + CSSOM-CRP

  • 2 kritische Ressourcen
  • 2 oder mehr Roundtrips für die minimale Länge des kritischen Pfads
  • 9 KB kritischer Byte

Wir benötigen sowohl den HTML- als auch den CSS-Code, um die Rendering-Struktur zu erstellen. Daher sind sowohl HTML als auch CSS wichtige Ressourcen: Der CSS-Code wird erst abgerufen, nachdem der Browser das HTML-Dokument abgerufen hat. Daher beträgt die Länge des kritischen Pfads mindestens zwei Roundtrips. Beide Ressourcen ergeben zusammengenommen 9 KB an kritischen Byte.

Fügen wir dem Mix nun 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 Ressource, die den Parser blockiert (also eine wichtige Ressource). Schlimmer noch: Um die JavaScript-Datei auszuführen, müssen wir CSSOM blockieren und auf CSSOM warten. Denken Sie daran, dass JavaScript das CSSOM abfragen kann und der Browser daher pausiert, bis style.css heruntergeladen und CSSOM erstellt wurde.

DOM, CSSOM, JavaScript-CRP

Wenn wir uns jedoch in der Praxis die "Netzwerkabfolge" dieser Seite ansehen, stellen Sie fest, dass die CSS- und JavaScript-Anfragen ungefähr zur gleichen Zeit initiiert werden. Der Browser ruft den HTML-Code ab, erkennt beide Ressourcen und initiiert beide Anfragen. Daher weist die obige Seite die folgenden kritischen Pfadmerkmale auf:

  • 3 kritische Ressourcen
  • 2 oder mehr Roundtrips für die minimale Länge des kritischen Pfads
  • 11 KB kritischer Byte

Wir haben jetzt drei kritische Ressourcen, die zusammen 11 KB kritischer Byte ergeben, aber unsere Länge des kritischen Pfads beträgt immer noch zwei Roundtrips, da wir CSS und JavaScript parallel übertragen können. Um die Merkmale Ihres kritischen Rendering-Pfads zu ermitteln, müssen Sie die kritischen Ressourcen identifizieren und verstehen, wie der Browser die Abrufe plant. Fahren wir mit unserem Beispiel fort.

Nach den Gesprächen mit unseren Website-Entwicklern stellen wir fest, dass der JavaScript-Code auf unserer Seite nicht blockiert werden muss. Wir haben einige Analysefunktionen und anderen Code, der das Rendern unserer Seite nicht blockieren muss. Mit diesem Wissen können wir dem Skript-Tag das Attribut "async" hinzufügen, um die Blockierung des Parsers aufzuheben:

<!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, asynchrone JavaScript-CRP

Ein asynchrones Skript bietet mehrere Vorteile:

  • Das Skript blockiert nicht mehr den Parser und ist nicht Teil des kritischen Rendering-Pfads.
  • Da keine anderen wichtigen Skripts vorhanden sind, muss das CSS-Ereignis das domContentLoaded-Ereignis nicht blockieren.
  • Je früher das domContentLoaded-Ereignis ausgelöst wird, desto früher kann die andere Anwendungslogik ausgeführt werden.

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

Wenn das CSS-Stylesheet schließlich nur zum Drucken benötigt wird, wie würde das aussehen?

<!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 blockierendes CSS und asynchrone JavaScript-CRP

Da die style.css-Ressource nur zum Drucken verwendet wird, muss sie vom Browser nicht blockiert werden, um die Seite zu rendern. Sobald die DOM-Erstellung abgeschlossen ist, verfügt der Browser über genügend Informationen, um die Seite zu rendern. Daher verfügt diese Seite nur über eine einzige kritische Ressource (das HTML-Dokument) und die minimale Länge des kritischen Rendering-Pfads beträgt einen Roundtrip.

Feedback