Chrome DevTools część 4 – Analiza wydajności aplikacji

Czas na kolejną część cyklu o Chrome DevTools. Dzisiaj skupię się na temacie analizy wydajności aplikacji internetowych.

Samo hasło „wydajność” jest dość ogólne. Wydaje mi się, że o aplikacji webowej można powiedzieć, że jest wydajna, jeśli czas pomiędzy akcją użytkownika, a reakcją aplikacji jest mały (najlepiej max 100ms). Moim zdaniem niezbędne jest też to, by program był stabilny przez cały czas użytkowania. No bo co nam po tym, że aplikacja jest szybka, jeśli po minucie używania wywala się z powodu braku pamięci 🙂

A co wpływa na wydajność od strony technicznej? Są to:

  • Zużycie zasobów
    • Obciążenie procesora
    • Ilość używanej pamięci RAM
    • W przypadku bardziej skomplikowanych graficznie programów będzie to również pamięć karty graficznej
  • Czas odpowiedzi serwera – nawet jeśli mamy super szybki frontend, ale nasz backend jest wolny jak włóż od tyłu, to dla użytkownika aplikacja ciągle będzie niewydajna

Mimo że, tworząc frontend, na szybkość wykonywania zapytań nie mamy wielkiego wpływu, to już na wydajność od strony zużycia zasobów tak. Zobaczmy więc, w jaki sposób, korzystając z DevTools można znaleźć i wyeliminować problemy z nadmiernym zużyciem pamięci RAM czy procesora.

1. Przygotowanie

Przed rozpoczęciem warto przejść do trybu incognito (chyba nie muszę nikomu tłumaczyć jak to zrobić ( ͡° ͜ʖ ͡°) ). Dzięki temu naszych testów nie będą zaśmiecały Chrome’owe rozszerzenia, które potrafią wykonywać jakieś dodatkowe skrypty.

2. Analiza zużycia pamięci

W celu analizy zużycia pamięci wykorzystam przykład wycieku pamięci. Mimo, że jest on dość prosty, to założę się, że część z Was kiedyś się z nim spotkała w takiej czy innej formie.

Program ten po naciśnięciu przycisku Add numbers co 10ms doda 1000 elementów div z losową liczbą i z zakresu 0-10000000 oraz zapisze dodany element w obiekcie numbers[i]. 

Jeżeli wśród tych tysiąca liczb będzie liczba 10000000 program wyświetli alert z napisem SUCCESS, podświetli znaleziony element na czerwono i zakończy losowanie. W przeciwnym razie usunie liczby z DOMu i losuje od początku.

const MAX_NUMBER = 10000000;
var numbers = {};
var interval = null;
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max min + 1)) + min;
}
function addRandomNumbers() {
for (var i = 0; i < 1000; i++) {
var number = getRandomInt(0, MAX_NUMBER);
var newDiv = $('<div>' + number + '</div>');
numbers[number] = newDiv;
$('#numbers').append(newDiv);
}
checkMaxPresent();
}
function checkMaxPresent() {
if (numbers[MAX_NUMBER]) {
numbers[MAX_NUMBER].css('background-color', 'red');
stop();
alert('SUCCESS');
return;
}
$('#numbers').html('');
}
function stop() {
if (interval) {
clearInterval(interval);
interval = null;
}
}
$('#click-me').click(function() {
interval = setInterval(addRandomNumbers, 10);
});

view raw
memory_leak.js
hosted with ❤ by GitHub

Odpalamy program, wszystko wydaje się być ok, gdyby nie to, że po kilku sekundach komputer zaczyna przymulać. Otwórzmy najpierw menedżer zadań (Opcje -> Więcej narzędzi -> Menedżer zadań) i zobaczmy, co się stało:

zrzut_zaznaczony.png
Menedżer zadań Chrome

Kurdelebele, nasz prosty program zużywa 1.8GB ramu :O Dlaczego? Sprawdźmy to, wykorzystując zakładkę Memory w DevTools. Możemy w tym celu wykorzystać genialny skrót klawiszowy, który ostatnio poznałem – ⌘ + ⇧ + P. Pokaże nam się okienko, dzięki któremu możemy dostać się do większości miejsc w DevTools. My wykorzystamy je do przejścia do zakładki Memory:

Developer_Tools_-_https___wordpress_com_post_mycodeworksiknowwhy_wordpress_com_604

Zakładkę Memory wykorzystamy, aby sprawdzić, na co zużywamy najwięcej pamięci. Użyjemy w tym celu opcji Take heap snapshot, aby wyświetlić zawartość sterty po kilkunastu sekundach pracy naszego programu:

snapshot.png
Snapshot sterty z wyciekiem pamięci

Tym, co rzuca się w oczy jest duża liczba obiektow typu HTMLDivElement. Jak to możliwe, że te div-y ciągle siedzą w pamięci, skoro za każdym razem czyścimy nasz widok z niepotrzebnych elementów za pomocą $(‚#numbers’).html(”) ? Otóż te div-y mimo , że nie są już w drzewie DOM, to ciągle istnieją do nich referencje z obiektu numbers, przez co nie mogą zostać usunięte z pamięci przez JavaScriptowy garbage collector. Możemy to poprawić wprowadzając jedną zmianę do funkcji checkMaxPresent:

function checkMaxPresent() {
if (numbers[MAX_NUMBER]) {
numbers[MAX_NUMBER].css('background-color', 'red');
stop();
alert('SUCCESS');
return;
}
$('#numbers').html('');
numbers = {}; // usuwa referencje do niepotrzebnych div-ów
}

view raw
memory_leak_fix.js
hosted with ❤ by GitHub

Resetując obiekt numbers usuwamy referencje do „martwych” obiektów DOMu i pozwalamy na zwolnienie zajmowanej przez nie pamięci. Zobaczmy, jak teraz wygląda nasz zrzut pamięci:

Zrzut ekranu 2017-09-23 o 16.34.28
Snapshot sterty po poprawce

Udało nam się pozbyć wycieku pamięci – na stercie nie ma już tysięcy obiektów typu HTMLDivElement.

3. Porównanie wydajności – zakładka „Performance”

Przeanalizujmy sobie jeszcze raz powyższy program przed i po usunięciu wycieku (czas w setInterval został zmieniony na 1000ms). Tym razem wykorzystamy do tego zakładkę Performance. Zakładka ta pozwala na bardziej kompleksową analizę całej aplikacji.

performance.png
Analiza przed i po usunięciu wycieku

Na powyższym obrazku widać kiedy procesor był najbardziej obciążony – było to co sekundę, gdy odpalała się funkcja addRandomNumbers. Oprócz tego można zauważyć, że wykres zużycia pamięci dla obydwu przypadków wygląda inaczej – w pierwszym krzywa jest wyraźnie rosnąca. W drugim natomiast po około 10s spada ona niemalże do stanu początkowego. Przybliżmy ten moment i zobaczmy co się stało:

garbacecollection.gif
Wywołanie garbage collectora

Do spadku przyczyniły się akcje DOM GC oraz Major GC, czyli z naszego programu nie wycieka pamięć.

4. Podsumowanie

Zakładka Performance pozwala nam zobaczyć jakie akcje powodują zmiany w zużyciu pamięci przez naszą aplikację na przestrzeni czasu, a zakładka Memory, informuje o tym, na co dokładnie w danej chwili zużywana jest pamięć.

Przykład, który przygotowałem, może i był prosty, ale pokazuje, że użycie tych dwóch narzędzi ułatwia znalezienie często trudnych do odkrycia problemów, jakimi są wycieki pamięci.