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:
- Często tworzone:
Bullet
Obiekt w grze typu bullet-hell .
- Głęboka struktura: drzewo decyzyjne dla implementacji AI.
Powinieneś użyć a Stack
jako swojej puli (w przeciwieństwie do większości implementacji, które używają a Queue
). Powodem tego jest to, że Stack
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 a LinkedList
.
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 zamiast O(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:
MyObject (G1) -> MyNestedObject (G1) -> MyFurtherNestedObject (G1)
// G1 Collection
MyNestObject (G2) -> MyFurtherNestedObject (G2)
// G2 Collection
MyFurtherNestedObject (G3)
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:
MyObject (G1) -> MyNestedObject (G1) -> MyFurtherNestedObject (G1)
// Dispose
MyObject (G1)
MyNestedObject (G1)
MyFurtherNestedObject (G1)
// G1 Collection
Wzorzec Disposer pomaga skonfigurować przewidywalny sposób, w jaki obiekty proszą o wyczyszczenie ich prywatnych pól. Na przykład:
public class MyClass : IDisposable
{
private MyNestedType _nested;
// A finalizer is only needed IF YOU CONTROL UNMANAGED RESOURCES
// ~MyClass() { }
public void Dispose()
{
_nested = null;
}
}