* Soweit ich weiß, basiert Unity3D für iOS auf der Mono-Laufzeit und Mono verfügt nur über eine generationsübergreifende Mark & Sweep-GC.
Dieses GC-System kann die GC-Zeit nicht umgehen, die das Spielsystem stoppt. Instanz-Pooling kann dies jedoch nicht vollständig reduzieren, da die Instanziierung in der Basisklassenbibliothek der CLR nicht gesteuert werden kann. Diese versteckten kleinen und häufigen Instanzen erhöhen schließlich die unbestimmte GC-Zeit. Wenn Sie die vollständige GC regelmäßig erzwingen, wird die Leistung erheblich beeinträchtigt (kann Mono die vollständige GC tatsächlich erzwingen?)
Wie kann ich diese GC-Zeit vermeiden, wenn ich Unity3D verwende, ohne die Leistung zu beeinträchtigen?
unity
performance
Eonil
quelle
quelle
Antworten:
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:
Bullet
Objekt in einem Bullet-Hell-Spiel .Sie sollten a
Stack
als Ihren Pool verwenden (im Gegensatz zu den meisten Implementierungen, die a verwendenQueue
). Der Grund dafür ist, dass Sie mit aStack
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 wolltenClear()
. 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 aLinkedList
.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 einerO(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:
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:Mit dem Disposer-Muster können Sie vorhersehbare Methoden einrichten, mit denen Objekte aufgefordert werden, ihre privaten Felder zu löschen. Beispielsweise:
quelle
In der Basisbibliothek findet nur eine sehr geringe dynamische Instanziierung statt, es sei denn, Sie rufen etwas auf, das dies erfordert. Die überwiegende Mehrheit der Speicherzuweisung und Freigabe erfolgt über Ihren eigenen Code, den Sie steuern können.
Soweit ich weiß, können Sie dies nicht gänzlich vermeiden. Sie können lediglich sicherstellen, dass Sie Objekte recyceln und bündeln, Strukturen anstelle von Klassen verwenden, das UnityGUI-System vermeiden und generell vermeiden, dass Sie während eines bestimmten Zeitraums neue Objekte im dynamischen Speicher erstellen wenn es auf leistung ankommt.
Sie können die Garbage Collection auch zu bestimmten Zeiten erzwingen, was möglicherweise nicht hilft. Einige Vorschläge finden Sie hier: http://unity3d.com/support/documentation/Manual/iphone-Optimizing-Scripts.html
quelle
GC.Collect()
= freier Speicher jetzt, aber sehr wahrscheinlich später Probleme.