Wie Sie bereits betont haben, verwenden die COMPACT Mono-Builds zum Glück einen Generations-GC (im Gegensatz zu den Microsoft-Builds wie WinMo / WinPhone / XBox, die nur eine flache Liste führen).
Wenn Ihr Spiel einfach ist, sollte der GC es in Ordnung bringen, aber hier sind einige Hinweise, die Sie sich ansehen sollten.
Vorzeitige Optimierung
Stellen Sie zunächst sicher, dass dies tatsächlich ein Problem für Sie ist, bevor Sie versuchen, es zu beheben.
Pooling teurer Referenztypen
Sie sollten Referenztypen bündeln, die Sie entweder häufig erstellen oder die über tiefe Strukturen verfügen. Ein Beispiel von jedem wäre:
- Oft erschaffen: Ein
Bullet
Objekt in einem Bullet-Hell-Spiel .
- Tiefe Struktur: Entscheidungsbaum für eine AI-Implementierung.
Sie sollten a Stack
als Ihren Pool verwenden (im Gegensatz zu den meisten Implementierungen, die a verwenden Queue
). Der Grund dafür ist, dass Sie mit a Stack
ein Objekt in den Pool zurückgeben und etwas anderes es sofort ergreift. Es hat eine viel höhere Chance, auf einer aktiven Seite zu sein - oder sogar im CPU-Cache, wenn Sie Glück haben. Es ist nur ein bisschen schneller. Begrenzen Sie außerdem immer die Größe Ihrer Pools (ignorieren Sie einfach das Einchecken, wenn Ihr Limit überschritten wurde).
Vermeiden Sie das Erstellen neuer Listen, um diese zu löschen
Erstellen Sie keine neuen, List
wenn Sie es tatsächlich wollten Clear()
. Sie können das Back-End-Array wiederverwenden und eine Menge Array-Zuordnungen und Kopien speichern. Ähnlich wie bei diesem Versuch, Listen mit einer sinnvollen Anfangskapazität zu erstellen (denken Sie daran, dies ist kein Limit - nur eine Anfangskapazität) - es muss nicht genau sein, nur eine Schätzung. Dies sollte grundsätzlich für jede Sammlungsart gelten - mit Ausnahme von a LinkedList
.
Verwenden Sie nach Möglichkeit Struct Arrays (oder Listen)
Sie profitieren nur wenig von der Verwendung von Strukturen (oder Werttypen im Allgemeinen), wenn Sie diese zwischen Objekten austauschen. Beispielsweise werden in den meisten "guten" Partikelsystemen die einzelnen Partikel in einem massiven Array gespeichert: Das Array und der Index werden anstelle des Partikels selbst herumgereicht. Der Grund, warum dies so gut funktioniert, ist, dass der GC, wenn er das Array sammeln muss, den Inhalt vollständig überspringen kann (es ist ein primitives Array - hier nichts zu tun). Anstatt also 10 000 Objekte zu betrachten, muss der GC nur 1 Array betrachten: enormer Gewinn! Auch dies funktioniert nur mit Werttypen .
Nach RoyT. Ich habe einige brauchbare und konstruktive Rückmeldungen gegeben, von denen ich glaube, dass ich sie weiter ausbauen muss. Sie sollten diese Technik nur anwenden, wenn Sie mit einer großen Anzahl von Entitäten zu tun haben (Tausende bis Zehntausende). Darüber hinaus es muss eine Struktur sein , die nicht müssen keine Referenztyp Felder haben und in einem explizit typisierte Array leben müssen. Im Gegensatz zu seinem Feedback platzieren wir es in einem Array, bei dem es sich sehr wahrscheinlich um ein Feld in einer Klasse handelt - was bedeutet, dass es auf dem Heap landet (wir versuchen nicht, eine Heap-Zuweisung zu vermeiden - lediglich GC-Arbeit zu vermeiden). Uns ist besonders wichtig, dass es sich um einen zusammenhängenden Speicherblock mit vielen Werten handelt, den der GC einfach in einer O(1)
Operation anstelle einer O(n)
Operation betrachten kann.
Sie sollten auch diese Arrays so nah an Ihren Start der Anwendung wie möglich verteilen die Chancen zu mildern Fragmentierung auftritt, oder übermäßige Arbeit als die GC diese Stücke zu bewegen versucht , um (und betrachten mit einer Hybrid - Linked - List anstelle der eingebauten in List
Art ).
GC.Collect ()
Dies ist definitiv DIE BESTE Möglichkeit, sich mit einem Generations-GC in den Fuß zu schießen (siehe: "Leistungsaspekte"). Sie sollten es nur aufrufen, wenn Sie eine EXTREME Menge an Müll erstellt haben - und die einzige Instanz, bei der dies ein Problem sein könnte, ist, nachdem Sie den Inhalt für eine Ebene geladen haben - und selbst dann sollten Sie wahrscheinlich nur die erste Generation sammeln ( GC.Collect(0);
) hoffentlich verhindern, dass Objekte in die dritte Generation befördert werden.
IDisposable und Field Nulling
Es lohnt sich, Felder auf Null zu setzen, wenn Sie kein Objekt mehr benötigen (mehr bei eingeschränkten Objekten). Der Grund liegt in den Details der Funktionsweise des GC: Es werden nur Objekte entfernt, die nicht verwurzelt sind (dh auf die verwiesen wird), auch wenn dieses Objekt nicht verwurzelt gewesen wäre, weil andere Objekte in der aktuellen Sammlung entfernt wurden ( Hinweis: Dies hängt vom GC ab Aroma im Gebrauch - einige reinigen tatsächlich Ketten). Wenn ein Objekt eine Sammlung überlebt, wird es sofort zur nächsten Generation befördert. Dies bedeutet, dass alle Objekte, die in Feldern herumliegen, während einer Sammlung befördert werden. Jede nachfolgende Generation ist in der Sammlung exponentiell teurer (und tritt so selten auf).
Nehmen Sie das folgende Beispiel:
MyObject (G1) -> MyNestedObject (G1) -> MyFurtherNestedObject (G1)
// G1 Collection
MyNestObject (G2) -> MyFurtherNestedObject (G2)
// G2 Collection
MyFurtherNestedObject (G3)
Wenn Sie MyFurtherNestedObject
ein Multi-Megabyte-Objekt enthalten, können Sie sicher sein, dass der GC es nicht lange ansieht, da Sie es versehentlich zu G3 hochgestuft haben. Vergleichen Sie das mit diesem Beispiel:
MyObject (G1) -> MyNestedObject (G1) -> MyFurtherNestedObject (G1)
// Dispose
MyObject (G1)
MyNestedObject (G1)
MyFurtherNestedObject (G1)
// G1 Collection
Mit dem Disposer-Muster können Sie vorhersehbare Methoden einrichten, mit denen Objekte aufgefordert werden, ihre privaten Felder zu löschen. Beispielsweise:
public class MyClass : IDisposable
{
private MyNestedType _nested;
// A finalizer is only needed IF YOU CONTROL UNMANAGED RESOURCES
// ~MyClass() { }
public void Dispose()
{
_nested = null;
}
}