* O ile mi wiadomo, Unity3D na iOS opiera się na środowisku wykonawczym Mono, a Mono ma tylko generacyjną markę i Sweep GC.
Ten system GC nie może uniknąć czasu GC, który zatrzymuje system gry. Pula wystąpień może to zmniejszyć, ale nie całkowicie, ponieważ nie możemy kontrolować tworzenia wystąpień w bibliotece klas podstawowych CLR. Te ukryte małe i częste instancje ostatecznie spowodują niedeterministyczny czas GC. Wymuszanie pełnego GC okresowo znacznie pogorszy wydajność (czy Mono może wymusić pełne GC?)
Jak więc uniknąć tego czasu GC, gdy korzystam z Unity3D bez znacznego obniżenia wydajności?
unity
performance
Eonil
źródło
źródło
Odpowiedzi:
Na szczęście, jak zauważyłeś, kompilacje COMPACT Mono używają generacyjnej GC (w przeciwieństwie do tych Microsoft, takich jak WinMo / WinPhone / XBox, które tylko utrzymują płaską listę).
Jeśli twoja gra jest prosta, GC powinien sobie z tym poradzić, ale oto kilka wskazówek, które warto rozważyć.
Przedwczesna optymalizacja
Najpierw upewnij się, że jest to problem, zanim spróbujesz go naprawić.
Łączenie drogich typów referencyjnych
Powinieneś połączyć typy referencji, które często tworzysz lub które mają głębokie struktury. Przykładem każdego z nich byłoby:
Bullet
Obiekt w grze typu bullet-hell .Powinieneś użyć a
Stack
jako swojej puli (w przeciwieństwie do większości implementacji, które używają aQueue
). Powodem tego jest to, żeStack
jeśli zwrócisz obiekt do puli i coś innego natychmiast go złapie; będzie miał znacznie większą szansę na bycie na aktywnej stronie - a nawet w pamięci podręcznej procesora, jeśli masz szczęście. Jest tylko trochę szybszy. Ponadto zawsze ograniczaj wielkość swoich pul (zignoruj „meldowanie”, jeśli limit został przekroczony).Unikaj tworzenia nowych list, aby je wyczyścić
Nie twórz nowego,
List
kiedy tak naprawdę chciałeśClear()
. Możesz ponownie użyć tablicy zaplecza i zaoszczędzić mnóstwo przydziałów tablicy i kopii. Podobnie jak w przypadku tej próby, twórz listy o znacznej pojemności początkowej (pamiętaj, że to nie jest limit - tylko pojemność początkowa) - nie musi być dokładna, tylko oszacowanie. Powinno to dotyczyć praktycznie każdego typu kolekcji - z wyjątkiem aLinkedList
.Jeśli to możliwe, używaj tablic strukturalnych (lub list)
Niewiele zyskujesz na stosowaniu struktur (lub ogólnie typów wartości), jeśli przenosisz je między obiektami. Na przykład w większości „dobrych” układów cząstek poszczególne cząstki są przechowywane w masywnym układzie: układ i wskaźnik są przekazywane wokół samej cząstki. Powodem, dla którego działa to tak dobrze, jest to, że gdy GC musi zebrać tablicę, może całkowicie pominąć zawartość (jest to prymitywna tablica - tutaj nie ma nic do zrobienia). Zamiast więc patrzeć na 10 000 obiektów, GC musi po prostu spojrzeć na 1 tablicę: ogromny zysk! Ponownie będzie to działać tylko z typami wartości .
Po RoyT. dostarczyła realnych i konstruktywnych informacji zwrotnych. Myślę, że muszę rozwinąć tę kwestię jeszcze bardziej. Tej techniki powinieneś używać tylko wtedy, gdy masz do czynienia z ogromną ilością bytów (tysiące do dziesiątek tysięcy). Ponadto musi to być struktura, która nie może zawierać żadnych pól typu odwołania i musi znajdować się w tablicy z wyraźnie określonym typem. W przeciwieństwie do jego opinii umieszczamy go w tablicy, która najprawdopodobniej jest polem w klasie - co oznacza, że wyląduje na stosie (nie próbujemy uniknąć przydziału stosu - po prostu unikamy pracy GC). Naprawdę zależy nam na tym, aby był to ciągły fragment pamięci z wieloma wartościami, na które GC może po prostu spojrzeć w
O(1)
operacji zamiastO(n)
operacji.Powinieneś także przydzielić te tablice jak najbliżej uruchomienia aplikacji, aby zminimalizować ryzyko wystąpienia fragmentacji lub nadmiernej pracy, gdy GC próbuje przenieść te fragmenty (i rozważ użycie hybrydowej listy zamiast wbudowanego
List
typu ).GC.Collect ()
To zdecydowanie NAJLEPSZY sposób na zastrzelenie się w stopę (patrz: „Rozważania na temat wydajności”) za pomocą pokoleniowego GC. Powinieneś wywoływać go tylko wtedy, gdy utworzyłeś EKSTREMALNĄ ilość śmieci - a jednym z przypadków, w którym może to być problem, jest tuż po załadowaniu zawartości dla poziomu - i nawet wtedy prawdopodobnie powinieneś zebrać tylko pierwszą generację (
GC.Collect(0);
) miejmy nadzieję, że zapobiegną promowaniu obiektów do trzeciej generacji.IDisposable i zerowanie pola
Warto zerować pola, gdy nie potrzebujesz już obiektu (bardziej w przypadku obiektów z ograniczeniami). Powodem są szczegóły działania GC: usuwa tylko obiekty, które nie są zrootowane (tj. Do których się odwołuje), nawet jeśli ten obiekt zostałby cofnięty z powodu usunięcia innych obiektów z bieżącej kolekcji ( uwaga: zależy to od GC smak w użyciu - niektóre faktycznie usuwają łańcuchy). Ponadto, jeśli obiekt przeżyje kolekcję, jest on natychmiast awansowany do następnej generacji - oznacza to, że wszelkie przedmioty pozostawione na polach zostaną awansowane podczas kolekcji. Każde kolejne pokolenie jest wykładniczo droższe w zbieraniu (i zdarza się rzadko).
Weź następujący przykład:
Jeśli
MyFurtherNestedObject
zawiera obiekt o wielkości wielu megabajtów, możesz mieć pewność, że GC nie będzie na niego długo patrzeć - ponieważ przypadkowo awansowałeś go do G3. Porównaj to z tym przykładem:Wzorzec Disposer pomaga skonfigurować przewidywalny sposób, w jaki obiekty proszą o wyczyszczenie ich prywatnych pól. Na przykład:
źródło
Bardzo mała dynamiczna instancja występuje automatycznie w bibliotece podstawowej, chyba że wywołasz coś, co tego wymaga. Zdecydowana większość alokacji i dezalokacji pamięci pochodzi z własnego kodu i można to kontrolować.
Jak rozumiem, nie można tego całkowicie uniknąć - wszystko, co możesz zrobić, to zapewnić recykling i pula obiektów tam, gdzie to możliwe, używać struktur zamiast klas, unikać systemu UnityGUI i ogólnie unikać tworzenia nowych obiektów w pamięci dynamicznej w czasie gdy liczy się wydajność.
Możesz także wymusić wyrzucanie elementów bezużytecznych w określonych momentach, co może, ale nie musi pomóc: zobacz niektóre sugestie tutaj: http://unity3d.com/support/documentation/Manual/iphone-Optimizing-Scripts.html
źródło
GC.Collect()
= wolna pamięć teraz, ale bardzo prawdopodobne problemy później.