Analizowanie wydajności krytycznej ścieżki renderowania

Aby wykrywać i rozwiązywać problemy z wydajnością na ścieżce renderowania, trzeba dobrze znać typowe pułapki. Przyjrzyjmy się, jak w praktyce wyodrębniać typowe wzorce wydajności, które pomogą Ci w optymalizacji stron.

Zoptymalizowanie krytycznej ścieżki renderowania umożliwia przeglądarce szybsze wyświetlanie strony – to przekłada się na większe zaangażowanie użytkowników, większą liczbę wyświetlonych stron i większą liczbę konwersji. Aby zminimalizować czas, przez jaki użytkownik widzi pusty ekran, musimy zoptymalizować to, które zasoby mają być wczytywane i w jakiej kolejności.

Aby zilustrować ten proces, zaczniemy od najprostszego przypadku i stopniowo rozbudowujemy naszą stronę, aby uwzględnić dodatkowe zasoby, style i logikę aplikacji. W tym celu zoptymalizujemy każdy przypadek i sprawdzimy, gdzie mogą wystąpić problemy.

Do tej pory skupialiśmy się wyłącznie na tym, co dzieje się w przeglądarce, gdy zasób (CSS, JS lub HTML) jest już dostępny do przetworzenia. Ignorujemy czas potrzebny na pobranie zasobu z pamięci podręcznej lub z sieci. Zakładamy, że:

  • Przesłanie danych w obie strony do serwera (opóźnienie propagacji) kosztuje 100 ms.
  • Czas odpowiedzi serwera wynosi 100 ms w przypadku dokumentu HTML i 10 ms w przypadku wszystkich innych plików.

Doświadczaj świata Hello

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

Wypróbuj

Zaczniemy od podstawowych znaczników HTML i pojedynczego obrazu. bez CSS ani JavaScript. Otwórzmy oś czasu sieci w Narzędziach deweloperskich w Chrome i sprawdźmy kaskadę zasobów:

CRP

Zgodnie z oczekiwaniami pobieranie pliku HTML trwało około 200 ms. Zwróć uwagę, że przezroczysta część niebieskiej linii oznacza czas, przez jaki przeglądarka czeka na połączenie z siecią bez odbierania bajtów odpowiedzi, a ciągła część to czas na zakończenie pobierania po otrzymaniu pierwszych bajtów odpowiedzi. Plik HTML do pobrania jest nieduży (<4K), więc aby pobrać cały plik, wystarczy jedna podróż w obie strony. W rezultacie pobieranie dokumentu HTML zajmuje około 200 ms, z czym połowa tego czasu to oczekiwanie na odpowiedź sieci, a druga połowa – na odpowiedź serwera.

Po udostępnieniu treści HTML przeglądarka analizuje bajty, konwertuje je na tokeny i tworzy drzewo DOM. Zwróć uwagę, że w Narzędziach deweloperskich w dodatku znajduje się czas zdarzenia DOMContentLoaded (216 ms), który odpowiada też niebieskiej linii pionowej. Przerwa między końcem pobierania kodu HTML a niebieską pionową linią (DOMContentLoaded) to czas potrzebny przeglądarce na zbudowanie drzewa DOM – w tym przypadku zaledwie kilka milisekund.

Zwróć uwagę, że nasze „świetne zdjęcie” nie zablokował(a) wydarzenia domContentLoaded. Okazuje się, że możemy utworzyć drzewo renderowania, a nawet pomalować stronę bez czekania na każdy zasób – nie wszystkie zasoby są niezbędne do szybkiego pierwszego wyrenderowania. Gdy mówimy o krytycznej ścieżce renderowania, zwykle mamy na myśli znaczniki HTML, CSS i JavaScript. Obrazy nie blokują początkowego renderowania strony, ale powinniśmy też jak najszybciej wyświetlić obrazy.

Mimo to zdarzenie load (nazywane też onload) jest zablokowane na obrazie: Narzędzia deweloperskie zgłaszają zdarzenie onload przy 335 ms. Pamiętaj, że zdarzenie onload wskazuje moment, w którym wszystkie zasoby wymagane przez stronę zostały pobrane i przetworzone. wtedy ikona wczytywania może przestać się obracać w przeglądarce (czerwona pionowa linia w kaskadzie).

Dodaj JavaScript i CSS

Korzystanie z aplikacji „Hello World” Strona wydaje się prosta, ale wiele szczegółów dzieje się w jej wnętrzu. W praktyce potrzebujemy czegoś więcej niż tylko kod HTML. Możliwe, że będziemy mieli arkusz stylów CSS i co najmniej jeden skrypt, aby wzbogacić naszą stronę o interaktywność. Dodajmy oba te elementy i zobaczmy, co się stanie:

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

Wypróbuj

Zanim dodasz JavaScript i arkusze CSS:

CRP DOM

JavaScript i CSS:

DOM, CSSOM, JS

Dodanie zewnętrznych plików CSS i JavaScriptu powoduje dodanie do naszej kaskady 2 dodatkowych żądań, które przeglądarka wysyła mniej więcej w tym samym czasie. Należy jednak pamiętać, że różnica między zdarzeniami domContentLoadedonload jest teraz znacznie mniejsza.

Co się stało?

  • W przeciwieństwie do przykładu zwykłego kodu HTML musimy też pobrać i przeanalizować plik CSS, aby zbudować obiekt CSSOM, a do zbudowania drzewa renderowania potrzebujemy zarówno obiektu DOM, jak i obiektu CSSOM.
  • Strona zawiera też plik JavaScript blokujący parser, więc zdarzenie domContentLoaded jest blokowane do czasu pobrania i przeanalizowania pliku CSS: ponieważ JavaScript może wysyłać zapytania do CSSOM, musimy zablokować plik CSS do czasu pobrania, zanim będziemy mogli wykonać JavaScript.

Co się stanie, jeśli zastąpimy skrypt zewnętrzny skryptem wbudowanym? Nawet jeśli skrypt jest wbudowany bezpośrednio w stronę, przeglądarka nie może go wykonać, dopóki nie zostanie utworzony CSSOM. Krótko mówiąc, wbudowany JavaScript blokuje parser.

Czy mimo zablokowania w kodzie CSS strona wbudowana w skrypt przyspiesza renderowanie strony? Spróbujmy i zobaczmy, co się stanie.

Zewnętrzny kod JavaScript:

DOM, CSSOM, JS

Wstawiony kod JavaScript:

DOM, CSSOM i inline JS

Chcemy o jedną prośbę mniej, ale okresy onload i domContentLoaded są takie same. Dlaczego? Wiemy, że nie ma znaczenia, czy JavaScript jest wbudowany czy zewnętrzny, ponieważ gdy tylko przeglądarka trafi w tag skryptu, blokuje się i czeka na utworzenie CSSOM. Ponadto w pierwszym przykładzie przeglądarka pobiera równolegle zarówno kod CSS, jak i JavaScript i kończy pobieranie w tym samym czasie. W tym przypadku umieszczenie kodu JavaScript niewiele nam pomoże. Istnieje jednak kilka strategii, które mogą przyspieszyć renderowanie strony.

Po pierwsze zwróć uwagę, że wszystkie wbudowane skrypty blokują parser, ale w przypadku skryptów zewnętrznych można dodać parametr „async” słowa kluczowego, aby odblokować parser. Cofnijmy wprowadzone zmiany i spróbujmy:

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

Wypróbuj

JavaScript blokujący parser (zewnętrzny):

DOM, CSSOM, JS

Asynchroniczny (zewnętrzny) JavaScript:

DOM, CSSOM, asynchroniczny JS

Dużo lepiej! Zdarzenie domContentLoaded jest wywoływane krótko po przeanalizowaniu kodu HTML. Przeglądarka wie, że nie ma blokować kodu JavaScript, a ponieważ nie ma też żadnych innych skryptów blokujących parsowanie, budowa obiektu CSSOM może przebiegać równolegle.

Moglibyśmy też umieścić w tekście zarówno CSS, jak i 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>

Wypróbuj

DOM, wbudowany CSS, wbudowany JS

Zwróć uwagę, że czas domContentLoaded jest w praktyce taki sam jak w poprzednim przykładzie; zamiast oznaczać kod JavaScript jako asynchroniczny, kod CSS i JS został umieszczony na stronie. Zwiększa to rozmiar strony HTML, ale zaletą jest to, że przeglądarka nie musi czekać na pobranie jakichkolwiek zewnętrznych zasobów. wszystko jest na stronie.

Jak widać, nawet w przypadku bardzo prostej strony optymalizacja krytycznej ścieżki renderowania nie jest prosta: trzeba zrozumieć wykres zależności między różnymi zasobami, określić, które z nich są „krytyczne”, Musimy wybrać jedną z różnych strategii umieszczania tych zasobów na stronie. Nie ma jednego rozwiązania tego problemu. każda strona jest inna. Aby znaleźć optymalną strategię, musisz samodzielnie wykonać podobny proces.

Zobaczmy, czy uda się nam zauważyć pewne ogólne wzorce skuteczności.

Wzorce wydajności

Najprostsza możliwa strona składa się tylko ze znaczników HTML, bez kodu CSS, JavaScript ani innych typów zasobów. Aby ją wyświetlić, przeglądarka musi zainicjować żądanie, poczekać na przybycie dokumentu HTML, przeanalizować go, utworzyć element DOM, a na koniec wyświetlić go na ekranie:

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

Wypróbuj

CRP – światowy świat

Czas między T0 a T1 określa czas przetwarzania sieci i serwera. W najlepszym przypadku (jeśli plik HTML jest mały) cały dokument jest pobierany w ramach jednej transmisji w obie strony. Ze względu na sposób działania protokołów transportowych TCP większe pliki mogą wymagać więcej przesyłania i odbierania danych. Dlatego w najlepszym przypadku strona powyżej ma jedną cykliczną (minimalną) krytyczną ścieżkę renderowania.

Teraz przyjrzyjmy się tej samej stronie, ale z zewnętrznym plikiem 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>

Wypróbuj

DOM + CSSOM CRP

Razem ponownie, pobieramy dokument HTML w obie strony i pobrane znaczniki wskazują, że potrzebujemy też pliku CSS. co oznacza, że przeglądarka musi wrócić do serwera i pobrać kod CSS, aby wyświetlić stronę na ekranie. W związku z tym przed wyświetleniem strony wymagane są co najmniej 2 przebiegi w obie strony. Jak już wspomnieliśmy, plik CSS może obejmować kilka sesji w obie strony, stąd nacisk na „minimum”.

Zdefiniujmy terminologię służącą do opisania krytycznej ścieżki renderowania:

  • Zasób krytyczny: zasób, który może zablokować początkowe renderowanie strony.
  • Długość ścieżki krytycznej: liczba cykli wymiany danych, czyli łączny czas wymagany do pobrania wszystkich zasobów krytycznych.
  • Baty krytyczne: łączna liczba bajtów wymaganych do pierwszego wyrenderowania strony, która stanowi sumę rozmiarów przesyłanych plików wszystkich zasobów krytycznych. W naszym pierwszym przykładzie z jedną stroną HTML był 1 zasób krytyczny (dokument HTML). długość ścieżki krytycznej była również równa jednej transmisji danych w obie strony (przy założeniu, że plik jest mały), a łączna liczba bajtów krytycznych odpowiada rozmiarowi transferu samego dokumentu HTML.

Porównajmy to z charakterystyką ścieżki krytycznej w przykładzie HTML i CSS powyżej:

DOM + CSSOM CRP

  • 2 najważniejsze zasoby
  • 2 lub więcej cykli wymiany danych dla minimalnej długości ścieżki krytycznej
  • 9 KB bajtów krytycznych

Do utworzenia drzewa renderowania potrzebujemy zarówno kodu HTML, jak i CSS. W efekcie zarówno HTML, jak i CSS są kluczowymi zasobami: kod CSS jest pobierany dopiero po tym, jak przeglądarka pobierze dokument HTML, a ścieżka krytyczna musi zawierać co najmniej 2 przebiegi w obie strony. Oba zasoby dają łącznie 9 KB bajtów krytycznych.

Teraz dodajmy do tego plik 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>

Wypróbuj

Dodaliśmy app.js, który jest zarówno zewnętrznym zasobem JavaScriptu, jak i zasobem blokującym parser (czyli krytycznym). Co gorsza, aby wykonać plik JavaScript, musimy zablokować i poczekać na kod CSSOM. pamiętaj, że JavaScript może wysyłać zapytania do CSSOM, dlatego przeglądarka jest wstrzymywana do czasu pobrania elementu style.css i wytworzenia arkusza CSSOM.

DOM, CSSOM, CRP JavaScript

W praktyce jednak, jeśli spojrzymy na „kaskadę sieci”, zobaczysz, że żądania CSS i JavaScript są inicjowane mniej więcej w tym samym czasie. przeglądarka pobiera kod HTML, wykrywa oba zasoby i inicjuje oba żądania. W efekcie powyższa strona ma te cechy ścieżki krytycznej:

  • 3 najważniejsze zasoby
  • 2 lub więcej cykli wymiany danych dla minimalnej długości ścieżki krytycznej
  • 11 KB bajtów krytycznych

Mamy teraz 3 zasoby kluczowe, które sumują się do 11 KB bajtów krytycznych, ale nasza ścieżka krytyczna to dwie procedury przesyłania danych w obie strony, ponieważ możemy równolegle przesyłać kod CSS i JavaScript. Określenie cech ścieżki renderowania krytycznych zasobów oznacza możliwość identyfikowania krytycznych zasobów oraz zrozumienia, jak przeglądarka będzie planować ich pobieranie. Wróćmy do naszego przykładu.

Po rozmowie z deweloperami witryn zdaliśmy sobie sprawę, że JavaScript, który umieściliśmy na naszej stronie, nie musi być blokowany. mamy trochę analityki i innego kodu, który nie musi blokować renderowania strony. Wiedząc to, możemy dodać komponent „asynchroniczny”, do tagu skryptu, aby odblokować parser:

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

Wypróbuj

DOM, CSSOM, asynchroniczny CRP JavaScript

Skrypt asynchroniczny ma kilka zalet:

  • Skrypt nie blokuje już parsowania i nie jest częścią ścieżki renderowania krytycznego.
  • Ponieważ nie ma innych krytycznych skryptów, skrypt CSS nie musi blokować zdarzenia domContentLoaded.
  • Im szybciej uruchomi się zdarzenie domContentLoaded, tym szybciej rozpocznie się wykonywanie innych logiki aplikacji.

W rezultacie nasza zoptymalizowana strona wraca do 2 zasobów kluczowych (HTML i CSS) o minimalnej długości ścieżki krytycznej wynoszącej 2 przebiegi w obie strony i łącznie 9 kB krytycznych bajtów.

Jak wyglądałby arkusz stylów CSS tylko do wydrukowania?

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

Wypróbuj

DOM, nieblokujący CSS i asynchroniczny kod CRP w języku JavaScript

Plik style.css jest używany tylko do drukowania, więc przeglądarka nie musi go blokować, aby renderować stronę. Oznacza to, że gdy tylko konstrukcja DOM zostanie zakończona, przeglądarka zyska wystarczającą ilość informacji do wyrenderowania strony. W rezultacie ta strona ma tylko 1 krytyczny zasób (dokument HTML), a minimalna długość ścieżki renderowania to 1 przesyłka w obie strony.

Prześlij opinię