Während eines Unity-Gesprächs über die Leistung sagte uns der Typ, wir sollten eine for each
Schleife vermeiden , um die Leistung zu steigern, und stattdessen die normale for-Schleife verwenden. Was ist der Unterschied zwischen for
und for each
in Bezug auf die Implementierung? Was macht for each
ineffizient?
unity
performance
Emad
quelle
quelle
for each
Schleifen etwas anderes Iterieren über als ein Array erzeugt Müll (Unity Versionen vor 5.5)Antworten:
Eine foreach-Schleife erstellt angeblich ein IEnumerator-Objekt aus der Sammlung, die Sie übergeben, und geht dann darüber hinweg. Also eine Schleife wie diese:
übersetzt sich in etwas wie
folgt : (einige Komplexität darüber, wie der Enumerator später entsorgt wird, entfällt)
Wenn dies naiv / direkt kompiliert wird, wird dieses Enumerator-Objekt auf dem Heap zugewiesen (was etwas Zeit in Anspruch nimmt), selbst wenn sein zugrunde liegender Typ eine Struktur ist - etwas, das als Boxen bezeichnet wird. Nachdem die Schleife abgeschlossen ist, wird diese Zuordnung zu Müll, um sie später zu bereinigen, und Ihr Spiel kann für einen Moment stottern, wenn sich genug Müll ansammelt, damit der Sammler einen vollständigen Sweep ausführen kann. Schließlich können die
MoveNext
Methode und dieCurrent
Eigenschaft mehr Funktionsaufrufschritte umfassen, als nur ein Element direkt mit abzurufencollection[i]
, wenn der Compiler diese Aktion nicht einbindet.Die Hellium-Links zu Unity-Dokumenten oben zeigen an, dass diese naive Form genau das ist, was in Unity vor Version 5.5 passiert , wenn über etwas anderes als ein natives Array iteriert wird.
In Version 5.6+ (einschließlich 2017-Versionen) ist dieses unnötige Boxen weg , und Sie sollten keine unnötige Zuordnung erfahren, wenn Sie einen konkreten Typ verwenden (z. B.
int[]
oderList<GameObject>
oderQueue<T>
).Sie erhalten weiterhin eine Zuordnung, wenn die betreffende Methode nur weiß, dass sie mit einer Art IList oder einer anderen Schnittstelle funktioniert. In diesen Fällen weiß sie nicht, mit welchem bestimmten Enumerator sie arbeitet, sodass sie zuerst in ein IEnumerator-Objekt gepackt werden muss .
Es besteht also eine gute Chance, dass dies ein alter Rat ist, der in der modernen Unity-Entwicklung nicht so wichtig ist. Unity-Entwickler wie wir können ein bisschen abergläubisch gegenüber Dingen sein, die in alten Versionen langsam / haarig waren. Nehmen Sie also jeden Rat dieser Form mit einem Körnchen Salz. ;) Der Motor entwickelt sich schneller als wir glauben.
In der Praxis hatte ich noch nie ein Spiel, das durch die Verwendung von foreach-Schleifen merkliche Langsamkeit oder Probleme mit der Speicherbereinigung aufwies, aber Ihr Kilometerstand kann variieren. Profilieren Sie Ihr Spiel auf der von Ihnen gewählten Hardware, um festzustellen, ob es sich überhaupt lohnt, sich Sorgen zu machen. Wenn Sie müssen, ist es ziemlich trivial, foreach-Schleifen später in der Entwicklung durch explizite for-Schleifen zu ersetzen, falls sich ein Problem manifestiert, damit ich nicht zu Beginn der Entwicklung auf Lesbarkeit und einfache Codierung verzichten würde.
quelle
List<MyCustomType>
undIEnumerator<MyCustomType> getListEnumerator() { }
sind in Ordnung, während eine MethodeIEnumerator getListEnumerator() { }
nicht wäre.Wenn a für jede Schleife, die für die Sammlung oder das Array eines Objekts verwendet wird (dh ein Array aller Elemente außer dem primitiven Datentyp), wird GC (Garbage Collector) aufgerufen, um am Ende von a für jede Schleife Speicherplatz der Referenzvariablen freizugeben.
Während die for-Schleife verwendet wird, um mithilfe eines Index über die Elemente zu iterieren, wirkt sich der primitive Datentyp im Vergleich zu einem nicht primitiven Datentyp nicht auf die Leistung aus.
quelle
temp
Variable hier kann auf dem Stapel zugewiesen werden, auch wenn die Auflistung voller Referenztypen ist (da es sich nur um eine Referenz auf die bereits auf dem Heap vorhandenen Einträge handelt und kein neuer Heapspeicher für eine Instanz dieses Referenztyps zugewiesen wird) Wir würden nicht erwarten, dass hier Müll anfällt. Der Enumerator selbst kann unter bestimmten Umständen eingerahmt sein und auf dem Heap landen. Diese Fälle gelten jedoch auch für primitive Datentypen.