Mam dość złożoną aplikację JavaScript, która ma główną pętlę wywoływaną 60 razy na sekundę. Wydaje się, że odbywa się dużo wyrzucania elementów bezużytecznych (na podstawie danych wyjściowych „piłokształtnych” z osi czasu pamięci w narzędziach deweloperskich Chrome) - co często wpływa na wydajność aplikacji.
Więc próbuję zbadać najlepsze praktyki, aby zmniejszyć ilość pracy, którą musi wykonać odśmiecacz. (Większość informacji, które udało mi się znaleźć w sieci, dotyczy unikania wycieków pamięci, co jest nieco innym pytaniem - moja pamięć się uwalnia, po prostu dzieje się za dużo śmieci). że sprowadza się to głównie do ponownego wykorzystania obiektów w jak największym stopniu, ale oczywiście diabeł tkwi w szczegółach.
Struktura aplikacji jest podzielona na `` klasy '' zgodnie z prostym dziedzictwem JavaScript Johna Resiga .
Myślę, że jednym problemem jest to, że niektóre funkcje można wywoływać tysiące razy na sekundę (ponieważ są używane setki razy podczas każdej iteracji głównej pętli) i być może lokalne zmienne robocze w tych funkcjach (łańcuchy, tablice itp.) może być problem.
Zdaję sobie sprawę z łączenia obiektów dla większych / cięższych obiektów (i używamy tego w pewnym stopniu), ale szukam technik, które można zastosować na całej planszy, szczególnie w odniesieniu do funkcji, które są wywoływane bardzo wiele razy w ciasnych pętlach .
Jakich technik mogę użyć, aby zmniejszyć ilość pracy, którą musi wykonać odśmiecacz?
A może także - jakie techniki można zastosować, aby określić, które obiekty są najczęściej zbierane jako śmieci? (Jest to bardzo duża baza kodu, więc porównywanie migawek stosu nie było zbyt owocne)
źródło
Odpowiedzi:
Wiele rzeczy, które musisz zrobić, aby zminimalizować rezygnację z GC, jest sprzecznych z tym, co jest uważane za idiomatyczny JS w większości innych scenariuszy, więc proszę, miej na uwadze kontekst podczas oceniania rad, które daję.
Alokacja odbywa się u współczesnych tłumaczy w kilku miejscach:
new
lub za pomocą składni literału[...]
lub{}
.(function (...) { ... })
.Object(myNumber)
lubNumber.prototype.toString.call(42)
Array.prototype.slice
.arguments
do refleksji nad listą parametrów.Unikaj robienia tego, a jeśli to możliwe, łącz obiekty i wykorzystuj je ponownie.
W szczególności zwróć uwagę na możliwości:
split
lub dopasowań wyrażeń regularnych, ponieważ każdy z nich wymaga alokacji wielu obiektów. Dzieje się tak często w przypadku kluczy do tabel wyszukiwania i dynamicznych identyfikatorów węzłów DOM. Na przykład,lookupTable['foo-' + x]
idocument.getElementById('foo-' + x)
oba wiążą alokację ponieważ istnieje powiązanie ciąg. Często można dołączyć klucze do długotrwałych obiektów zamiast ponownego łączenia. W zależności od przeglądarek, które chcesz obsługiwać, możesz mieć możliwośćMap
bezpośredniego używania obiektów jako kluczy.try { op(x) } catch (e) { ... }
zróbif (!opCouldFailOn(x)) { op(x); } else { ... }
.JSON.stringify
które używa wewnętrznego natywnego bufora do gromadzenia zawartości zamiast przydzielania wielu obiektów.arguments
od funkcji, które używają, które muszą tworzyć obiekt podobny do tablicy po wywołaniu.Zasugerowałem użycie
JSON.stringify
do tworzenia wychodzących wiadomości sieciowych. Przetwarzanie komunikatów wejściowych przy użyciuJSON.parse
oczywiście wiąże się z alokacją, a wiele z nich w przypadku dużych wiadomości. Jeśli możesz reprezentować wiadomości przychodzące jako tablice prymitywów, możesz zaoszczędzić wiele przydziałów. Jedyną inną wbudowaną wersją, wokół której można zbudować parser, który nie przydziela, jestString.prototype.charCodeAt
. Parser dla złożonego formatu, który używa tylko tego, co będzie piekielne do czytania.źródło
JSON.parse
obiekty d przydzielają mniej (lub równo) miejsca niż łańcuch wiadomości?Te narzędzia deweloperskie Chrome mają bardzo ładny funkcję śledzenia dla alokacji pamięci. Nazywa się to Oś czasu pamięci. W tym artykule opisano kilka szczegółów. Przypuszczam, że właśnie o tym mówisz, czy „piłokształtny”? Jest to normalne zachowanie dla większości środowisk uruchomieniowych z GC. Alokacja przebiega do momentu osiągnięcia progu użycia wyzwalającego kolekcję. Zwykle istnieją różne rodzaje kolekcji na różnych progach.
Wyrzucanie elementów bezużytecznych znajduje się na liście zdarzeń skojarzonej ze śledzeniem wraz z ich czasem trwania. Na moim raczej starym notatniku kolekcje efemeryczne pojawiają się z prędkością około 4 MB i trwają 30 ms. To są 2 z twoich iteracji pętli 60 Hz. Jeśli jest to animacja, prawdopodobnie zacinają się kolekcje 30 ms. Powinieneś zacząć tutaj, aby zobaczyć, co dzieje się w twoim środowisku: gdzie jest próg zbierania i jak długo trwa twoje zbieranie. Daje to punkt odniesienia do oceny optymalizacji. Ale prawdopodobnie nie zrobisz nic lepszego niż zmniejszenie częstotliwości jąkania poprzez spowolnienie tempa alokacji, wydłużenie odstępu między kolekcjami.
Następnym krokiem jest użycie Profiles | Funkcja Record Heap Allocations umożliwia generowanie katalogu alokacji według typu rekordu. To szybko pokaże, które typy obiektów zużywają najwięcej pamięci w okresie śledzenia, co jest równoważne szybkości alokacji. Skoncentruj się na nich w kolejności malejącej stawki.
Techniki te nie są fizyką rakietową. Unikaj przedmiotów w pudełkach, jeśli możesz to zrobić z rozpakowanym. Użyj zmiennych globalnych, aby przechowywać i ponownie używać pojedynczych obiektów, zamiast przydzielać nowe w każdej iteracji. Połącz popularne typy obiektów na bezpłatnych listach, zamiast je porzucać. Wyniki konkatenacji ciągów pamięci podręcznej, które prawdopodobnie będą ponownie używane w przyszłych iteracjach. Unikaj alokacji tylko po to, aby zwrócić wyniki funkcji, ustawiając zmienne w otaczającym zakresie. Aby znaleźć najlepszą strategię, będziesz musiał rozważyć każdy typ obiektu w jego własnym kontekście. Jeśli potrzebujesz pomocy ze szczegółami, opublikuj edycję opisującą szczegóły wyzwania, na które patrzysz.
Odradzam wypaczanie twojego normalnego stylu kodowania w całej aplikacji, próbując wyprodukować mniej śmieci. Z tego samego powodu nie powinieneś przedwcześnie optymalizować prędkości. Większość twojego wysiłku plus znaczna część dodatkowej złożoności i niejasności kodu będzie bez znaczenia.
źródło
request animation frame
,animation frame fired
, icomposite layers
. Nie mam pojęcia, dlaczego nie widzęGC Event
tak, jak ty (to jest na najnowszej wersji chrome, a także canary).@342342
icode relocation info
.Zgodnie z ogólną zasadą chciałbyś przechowywać jak najwięcej pamięci podręcznej i robić jak najmniej tworzenia i niszczenia dla każdego uruchomienia pętli.
Pierwszą rzeczą, która przychodzi mi do głowy, jest ograniczenie użycia anonimowych funkcji (jeśli takie masz) w głównej pętli. Łatwo byłoby też wpaść w pułapkę tworzenia i niszczenia obiektów, które są przekazywane do innych funkcji. W żadnym wypadku nie jestem ekspertem od javascript, ale wyobrażam sobie, że to:
var options = {var1: value1, var2: value2, ChangingVariable: value3}; function loopfunc() { //do something } while(true) { $.each(listofthings, loopfunc); options.ChangingVariable = newvalue; someOtherFunction(options); }
działałby znacznie szybciej niż to:
while(true) { $.each(listofthings, function(){ //do something on the list }); someOtherFunction({ var1: value1, var2: value2, ChangingVariable: newvalue }); }
Czy Twój program ma kiedykolwiek przestoje? Może potrzebujesz, aby działał płynnie przez sekundę lub dwie (np. W przypadku animacji), a potem miał więcej czasu na przetworzenie? Jeśli tak jest, mógłbym zobaczyć, jak pobiera się obiekty, które normalnie byłyby zbierane jako śmieci przez całą animację i zachowuje odniesienie do nich w jakimś obiekcie globalnym. Następnie, gdy animacja się zakończy, możesz wyczyścić wszystkie odniesienia i pozwolić modułowi odśmiecania pamięci wykonać swoją pracę.
Przepraszam, jeśli to wszystko jest trochę trywialne w porównaniu z tym, co już próbowałeś io czym myślałeś.
źródło
Zrobiłbym jeden lub kilka obiektów w
global scope
(gdzie jestem pewien, że garbage collector nie może ich dotykać), a następnie spróbuję refaktoryzować moje rozwiązanie, aby użyć tych obiektów do wykonania zadania, zamiast używać zmiennych lokalnych .Oczywiście nie można tego zrobić wszędzie w kodzie, ale generalnie jest to mój sposób na uniknięcie garbage collectora.
PS Może to sprawić, że ta konkretna część kodu będzie nieco trudniejsza do utrzymania.
źródło