Analizowanie wydajności krytycznej ścieżki renderowania

Opublikowano: 31 marca 2014 r.

Aby wykrywać i rozwiązywać problemy z wydajnością na ścieżce renderowania, trzeba dobrze znać typowe pułapki. Wprowadzenie, które pomoże Ci zidentyfikować typowe wzorce skuteczności, ułatwi Ci optymalizację stron.

Optymalizacja ścieżki renderowania krytycznego pozwala przeglądarce wyświetlać stronę tak szybko, jak to możliwe. Szybsze wczytywanie stron zwiększa zaangażowanie użytkowników, liczbę wyświetlanych stron i poprawia skuteczność konwersji. Aby zminimalizować czas, jaki użytkownik spędza na oglądaniu pustego ekranu, musimy zoptymalizować kolejność wczytywania zasobów.

Aby zilustrować ten proces, zacznij od najprostszego możliwego przypadku i stopniowo rozbudowuj stronę, aby zawierała 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 po tym, jak zasób (plik CSS, JS lub HTML) jest dostępny do przetwarzania. Zignorowaliśmy czas potrzebny na pobranie zasobu z pamięci podręcznej lub z sieci. Zakładamy, że:

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

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>

Wypróbuj

Zacznij od podstawowego oznaczenia HTML i jednego obrazu bez użycia kodu CSS ani JavaScript. Następnie otwórz panel Sieć w Narzędziach deweloperskich w Chrome i sprawdź uzyskaną 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 efekcie pobieranie dokumentu HTML trwa około 200 ms – połowę czasu oczekiwania na połączenie z siecią, a druga połowa czasu oczekiwania 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ło zdarzenia domContentLoaded. Okazuje się, że możemy zbudować drzewo renderowania i nawet wyrenderować stronę bez oczekiwania na każdy komponent na stronie: nie wszystkie zasoby są niezbędne do szybkiego wyrenderowania strony. W zasadzie, gdy mówimy o krytycznej ścieżce renderowania, mamy na myśli znaczniki HTML, CSS i JavaScript. Obrazy nie zasłaniają początkowego renderowania strony, ale powinniśmy też jak najszybciej je wyrenderować.

Zdarzenie load (znane też jako onload) jest jednak zablokowane na obrazie: Narzędzie deweloperów odnotowuje zdarzenie onload o długości 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).

Dodawanie kodu JavaScript i CSS

Strona „Hello World” może wydawać się prosta, ale pod maską dzieje się dużo. W praktyce potrzebujemy czegoś więcej niż samego kodu HTML: prawdopodobnie będziemy mieć arkusz stylów CSS i co najmniej jeden skrypt, aby dodać stronie interaktywność. Dodaj do nich oba te elementy, aby zobaczyć, 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

Przed dodaniem kodu JavaScript i CSS:

CRP DOM

JavaScript i CSS:

DOM, CSSOM, JS

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

Co się stało?

  • W przeciwieństwie do zwykłego przykładu w języku HTML musimy też pobrać i przeanalizować plik CSS, aby utworzyć CSSOM, a do utworzenia drzewa renderowania potrzebujemy zarówno DOM, jak i CSSOM.
  • Strona zawiera też plik JavaScript, który blokuje parsowanie, więc zdarzenie domContentLoaded jest blokowane, dopóki plik CSS nie zostanie pobrany i przetworzony. Ponieważ kod JavaScript może wysyłać zapytania do CSSOM, musimy zablokować plik CSS, dopóki nie zostanie pobrany, zanim będziemy mogli wykonać kod 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 też blokuje parsowanie.

Czy mimo blokowania przez CSS umieszczenie skryptu w głównym kodzie strony przyspieszy renderowanie? Wypróbuj i zobacz, co się stanie.

Zewnętrzny kod JavaScript:

DOM, CSSOM, JS

Wstawiony kod JavaScript:

DOM, CSSOM i JS w tekście

Wysyłamy o 1 żądanie mniej, ale czasy onloaddomContentLoaded są w zasadzie takie same. Dlaczego? Wiemy, że nie ma znaczenia, czy kod JavaScript jest wbudowany, czy zewnętrzny, ponieważ gdy tylko przeglądarka natrafi na tag skryptu, blokuje go i czeka, aż zostanie utworzony obiekt CSSOM. W pierwszym przykładzie przeglądarka pobiera jednocześnie pliki CSS i JavaScript, a ich pobieranie kończy się mniej więcej w tym samym czasie. W tym przypadku umieszczenie kodu JavaScript niewiele nam pomoże. Istnieje jednak kilka strategii, które mogą przyspieszyć renderowanie strony.

Przede wszystkim pamiętaj, że wszystkie wbudowane skrypty blokują parser, ale w przypadku skryptów zewnętrznych można dodać atrybut async, aby go odblokować. Odwróć wstawienie kodu i spróbuj:

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

Blokowanie parsowania (zewnętrzny) JavaScript:

DOM, CSSOM, JS

Asynchroniczny (zewnętrzny) kod 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 potrzeby blokowania kodu JavaScript, a ponieważ nie ma też żadnych innych skryptów blokujących parsowanie, budowa modelu 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 taki sam jak w poprzednim przykładzie. Zamiast oznaczać kod JavaScript jako asynchroniczny, wbudowaliśmy zarówno kod CSS, jak i JS w samej 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 widzisz, nawet w przypadku bardzo prostej strony optymalizacja ścieżki renderowania krytycznego nie jest prostym zadaniem: musisz zrozumieć zależność między różnymi zasobami, określić, które z nich są „krytyczne”, a potem wybrać jedną z różnych strategii ich włączania na stronie. Nie ma jednego uniwersalnego rozwiązania tego problemu, ponieważ każda strona jest inna. Aby znaleźć optymalną strategię, musisz samodzielnie wykonać podobny proces.

Spróbujmy jednak cofnąć się i określić 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 wyrenderować tę stronę, przeglądarka musi zainicjować żądanie, poczekać na dostarczenie dokumentu HTML, przeanalizować go, utworzyć DOM, a na koniec wyrenderować 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 „Hello world”

Czas między T0 a T1 określa czas przetwarzania sieci i serwera. W najlepszym przypadku (jeżeli plik HTML jest mały), cały dokument zostanie pobrany w ramach jednej operacji przesyłania danych w obie strony. Ze względu na sposób działania protokołów transportowych TCP większe pliki mogą wymagać więcej przesyłań w obie strony. W efekcie w najlepszym przypadku strona ta ma ścieżkę renderowania w obie strony (minimalnie) na potrzeby renderowania krytycznego.

Teraz weź pod uwagę tę samą stronę, 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

Ponownie uruchamiamy pętlę sieciową, aby pobrać dokument HTML, a potem pobrany znacznik informuje nas, że potrzebujemy też pliku CSS. Oznacza to, że przeglądarka musi wrócić na serwer i pobrać plik CSS, zanim będzie mogła wyrenderować stronę na ekranie. W związku z tym przed wyświetleniem strony wymagane są co najmniej 2 przebiegi w obie strony. Plik CSS może wymagać kilku przesyłań w obie strony, dlatego kładziemy nacisk na „minimum”.

Oto kilka terminów, których używamy do opisu krytycznej ścieżki renderowania:

  • Critical Resource (Critical Resource): zasób, który może blokować 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.

Teraz porównajmy to z cechami krytycznymi ścieżek z poprzedniego przykładu kodu HTML i CSS:

DOM + CRP typu CSSOM

  • 2 najważniejsze zasoby
  • 2 lub więcej przejazdów w obie strony na minimalnej długości ścieżki krytycznej
  • 9 KB danych krytycznych

Do tworzenia 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 dodaj dodatkowy 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 zewnętrznym komponentem JavaScriptu na stronie i zasobom blokującym parsowanie (czyli krytycznym). Co gorsza, aby wykonać plik JavaScript, musimy zablokować i odczekać na obiekt CSSOM. Pamiętaj, że JavaScript może wysyłać zapytania do obiektu CSSOM, a przeglądarka wstrzymuje się, dopóki plik style.css nie zostanie pobrany i nie zostanie utworzony obiekt CSSOM.

DOM, CSSOM, JavaScript CRP

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 związku z tym strona widoczna na poprzednim obrazie ma te cechy ścieżki krytycznej:

  • 3 najważniejsze zasoby
  • 2 lub więcej przejazdów w obie strony na minimalnej długości ścieżki krytycznej
  • 11 KB bajtów krytycznych

Mamy teraz 3 krytyczne zasoby, które łącznie zajmują 11 KB, ale długość krytycznej ścieżki to nadal 2 przesyłanie w obie strony, ponieważ możemy przesyłać kod CSS i JavaScript równolegle. Określenie cech ścieżki renderowania oznacza możliwość identyfikowania kluczowych zasobów oraz zrozumienie, jak przeglądarka będzie planować ich pobieranie.

Po rozmowie z programistami naszej witryny zdaliśmy sobie sprawę, że kod JavaScript, który umieściliśmy na stronie, nie musi być blokowany. Zawiera on kod analityczny i inne elementy, które nie muszą blokować renderowania strony. Dzięki temu możemy dodać do elementu <script> atrybut async, 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, asynchroniczne CRP JavaScript

Skrypt asynchroniczny ma kilka zalet:

  • Skrypt nie blokuje już parsera i nie należy do krytycznej ścieżki renderowania.
  • Ponieważ nie ma innych krytycznych skryptów, plik 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 znów zawiera 2 kluczowe zasoby (HTML i CSS), a minimalna długość ścieżki krytycznej to 2 przesyłanie w obie strony. Łącznie 9 KB danych krytycznych.

Gdyby arkusz stylów CSS byłby potrzebny tylko do wydrukowania, jak wyglądałby?

<!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 ma wystarczającą ilość informacji, aby wyrenderować stronę. W rezultacie ta strona ma tylko 1 zasób krytyczny (dokument HTML), a minimalna długość ścieżki renderowania krytycznego to 1 przesył.

Prześlij opinię