Kod JavaScript pamięci statycznej z pulami obiektów

Wstęp

Otrzymujesz więc e-maila z informacją o tym, jak gra internetowa lub aplikacja internetowa działa ze słabą wydajnością po pewnym czasie, przeszukujesz kod i nie widzisz niczego, co wyróżniałoby się w pamięci, dopóki nie otworzysz narzędzi poprawiających wydajność pamięci w Chrome i zobaczysz:

Migawka z osi czasu wspomnienia

Jeden ze współpracowników chichocze, bo zdaje sobie sprawę, że masz problem z wydajnością pamięci.

W widoku wykresu pamięci ten wzorzec piłek zębatych mówi nam o potencjalnie krytycznym problemie z wydajnością. W miarę zwiększania wykorzystania pamięci obszar wykresu będzie się powiększał w zapisie na osi czasu. Jeśli wykres nagle się zaniża, jest to instancja, w której kolekcjoner śmieci uruchomił się i wyczyścił wskazane obiekty pamięci.

Czym są piłe zęby

Na takim wykresie widać, że wystąpiło wiele zdarzeń związanych z wyrzucaniem śmieci, które mogą mieć negatywny wpływ na działanie Twoich aplikacji internetowych. Z tego artykułu dowiesz się, jak kontrolować wykorzystanie pamięci, ograniczając jej wpływ na wydajność.

Koszty wywozu odpadów i wydajności

Model pamięci JavaScriptu opiera się na technologii nazywanej kolejnikiem śmieci. W przypadku wielu języków programista jest bezpośrednio odpowiedzialny za przydzielanie i zwalnianie pamięci systemowej sterty pamięci. System zbierający śmieci zarządza jednak tym zadaniem w imieniu programisty, co oznacza, że obiekty nie są bezpośrednio uwalniane z pamięci, gdy programer ją cofnie, ale raczej w późniejszym czasie, gdy heurystyka GC uzna, że powinno to być korzystne. Ten proces decyzyjny wymaga, aby GC przeprowadzał analizy statystyczne aktywnych i nieaktywnych obiektów, co zajmuje blok czasu na wykonanie.

Zbieranie odpadów jest często przedstawiane jako przeciwieństwo ręcznego zarządzania pamięcią, które wymaga od programisty określenia, które obiekty usunąć i zwrócić do systemu pamięci.

Proces, w ramach którego GC odzyskuje pamięć, nie jest wolna. Zazwyczaj zmniejsza dostępną wydajność, poświęcając określony blok czasu na wykonanie swoich zadań. Poza tym to system decyduje, kiedy je uruchomić. Nie masz wpływu na to działanie. W dowolnym momencie podczas wykonywania kodu może wystąpić puls GC, co spowoduje zablokowanie wykonania kodu do momentu jego zakończenia. Czas trwania tego pulsu jest ogólnie nieznany. Jego uruchomienie zajmie trochę czasu, w zależności od tego, jak program wykorzystuje pamięć w danym momencie.

Aplikacje o wysokiej wydajności opierają się na stałych granicach wydajności, aby zapewnić użytkownikom płynne działanie. Systemy zbierania śmieci mogą zawrzeć ten cel, ponieważ mogą działać w losowych momentach i przez losowy czas, wykorzystując dostatecznie dużo czasu, jaki aplikacja potrzebuje do osiągnięcia swoich celów w zakresie wydajności.

Zmniejsz liczbę rezygnacji z pamięci, obniż podatki za wywóz śmieci

Jak już wspomniano, puls GC zostanie ustalony, gdy na podstawie zestawu heurystyki stwierdzono, że istnieje dostateczna liczba nieaktywnych obiektów, które mogłyby być korzystne. Dlatego kluczem do skrócenia czasu poświęcanego przez niego na korzystanie z aplikacji jest wyeliminowanie jak największej liczby przypadków tworzenia i publikowania obiektów. Proces tworzenia/uwalniania obiektu często nazywany jest „rezygnacją pamięci”. Jeśli możesz zmniejszyć ilość pamięci w trakcie działania aplikacji, skrócisz też czas wykonywania kodu przez GC. Musisz więc usunąć lub zmniejszyć liczbę tworzonych i zniszczonych obiektów, co w efekcie musi przestać przydzielać pamięć.

Ten proces przeniesie wykres pamięci z tego miejsca :

Migawka z osi czasu wspomnienia

na:

Kod JavaScript pamięci statycznej

W tym modelu można zauważyć, że wykres nie ma już wzorca podobnego do wycinków, lecz na początku znacznie rośnie, a z czasem powoli się zwiększa. Jeśli masz problemy z wydajnością z powodu rezygnacji pamięci, utwórz ten typ wykresu.

W kierunku JavaScriptu ze statyczną pamięcią

Statyczny kod JavaScript pamięci statycznej to technika obejmująca wstępne przydzielanie na początku aplikacji całej pamięci potrzebnej przez cały okres jej użytkowania oraz zarządzanie nią podczas wykonywania, ponieważ obiekty nie są już potrzebne. Można to osiągnąć w kilku prostych krokach:

  1. Dostosuj aplikację, aby określić maksymalną liczbę wymaganych obiektów aktywnej pamięci (na typ) dla różnych scenariuszy użycia
  2. Wdróż ponownie kod, aby wstępnie przydzielić maksymalną ilość, a potem ręcznie pobierz/zwolnij kod, zamiast korzystać z pamięci głównej.

Tak naprawdę osiągnięcie nr 1 wymaga od nas trochę popracowania w trakcie drugiego etapu, więc zacznijmy od tego.

Pula obiektów

Mówiąc najprościej, pula obiektów to proces przechowywania zestawu nieużywanych obiektów, które mają ten sam typ. Gdy potrzebujesz nowego obiektu dla kodu, zamiast przydzielać nowy ze sterty pamięci systemowej, możesz odświeżyć jeden z nieużywanych obiektów z puli. Gdy kod zewnętrzny zostanie wykonany i nie zostanie udostępniony w głównej pamięci, obiekt wraca do puli. Obiekt nigdy nie zostanie usunięty z kodu (czyli usunięty). Nie będzie wtedy system do gromadzenia odpadów. Korzystanie z puli obiektów pozwala programistom przejąć kontrolę nad pamięcią, ograniczając wpływ modułu śmieci na wydajność.

Aplikacja utrzymuje heterogeniczny zestaw typów obiektów, dlatego prawidłowe wykorzystanie pul obiektów wymaga posiadania jednej puli obiektów na typ, w którym w trakcie działania aplikacji występuje duża liczba rezygnacji.

var newEntity = gEntityObjectPool.allocate();
newEntity.pos = {x: 215, y: 88};

//..... do some stuff with the object that we need to do

gEntityObjectPool.free(newEntity); //free the object when we're done
newEntity = null; //free this object reference

W przypadku znacznej większości aplikacji ostatecznie osiągniesz wyższy poziom, jeśli chodzi o przydzielanie nowych obiektów. Po kilku uruchomieniach aplikacji powinno Ci się dobrze zorientować, czym jest ten górny limit, i na samym początku przydzielić mu odpowiednią liczbę obiektów.

Wstępne przydzielanie obiektów

Wdrożenie puli obiektów w projekcie zapewni teoretyczną maksymalną liczbę obiektów wymaganych w czasie działania aplikacji. Po przeprowadzeniu różnych scenariuszy testowania w witrynie możesz zorientować się, jakie wymagania dotyczące pamięci będą potrzebne, a następnie skatalogować dane i przeanalizować je, aby określić, jakie są górne ograniczenia dotyczące pamięci w przypadku danej aplikacji.

Następnie w wersji dostawy aplikacji możesz ustawić etap inicjowania, aby wstępnie wypełnić wszystkie pule obiektów określoną ilością. Ta czynność spowoduje przeniesienie wszystkich inicjowania obiektów na początek aplikacji i zmniejszy liczbę przydziałów występujących dynamicznie podczas jej wykonywania.

function init() {
  //preallocate all our pools. 
  //Note that we keep each pool homogeneous wrt object types
  gEntityObjectPool.preAllocate(256);
  gDomObjectPool.preAllocate(888);
}

Wybrana kwota ma duży wpływ na działanie aplikacji. Czasami teoretyczne maksimum nie jest najlepszym rozwiązaniem. Na przykład wybranie średniej wartości maksymalnej może zmniejszyć ilość pamięci dla użytkowników niezaawansowanych.

Z dala od sukcesów

Istnieje cała klasyfikacja aplikacji, w których przypadku statyczne wzorce wzrostu pamięci mogą być korzystne. Jak zauważa Renato Mangini, specjalista Chrome DevRel, ma jednak kilka wad.

Podsumowanie

Jedną z powodów, dla których JavaScript jest idealny dla użytkowników internetu, jest fakt, że jest to szybki, atrakcyjny i łatwy w obsłudze język. Wynika to głównie z niewielkiej bariery nakładania ograniczeń składniowych oraz przez obsługę problemów z pamięcią w Twoim imieniu. Możesz więc pisać kod, a urządzenie zajmie się brudną pracą. Jednak w przypadku aplikacji internetowych o wysokiej wydajności, takich jak gry w formacie HTML5, tryby GC mogą często zajmować niezbędną liczbę klatek, co zmniejsza wygodę użytkownika. Dzięki ostrożnym narzędziom i wdrożeniu pul obiektów możesz zmniejszyć obciążenie liczby klatek, a przy tym zaoszczędzić czas, aby uzyskać jeszcze lepsze wyniki.

Kod źródłowy

W internecie można znaleźć wiele implementacji pul obiektów, więc nie będziemy Was zanudzać kolejnymi. zamiast tego przekieruję Cię do nich. Każda z nich wiąże się z określonymi niuansami związanymi z implementacją. Jest to ważne, ponieważ wraz z zastosowaniem aplikacji każde z nich może mieć inne potrzeby w zakresie implementacji.

Źródła