→
Was ist ECS?→
Was ist SchauspielerIch habe oft gehört, wie gut die
ECS- Vorlage ist und dass
Jobs und
Burst aus der
Unity- Bibliothek die Lösung für alle Leistungsprobleme sind. Um nicht jedes Mal das Wort "wahrscheinlich" und "vielleicht" hinzuzufügen und die Geschwindigkeit des Codes zu besprechen, habe ich beschlossen, alles persönlich zu überprüfen.
Mein Ziel war es, offen zu machen, wie schnell dieses Entwicklungswerkzeug ist und ob Parallelisierung für Berechnungen verwendet werden soll. Und wenn
ja , ist es besser,
Unity.Jobs oder
System.Threading zu verwenden ? Gleichzeitig habe ich herausgefunden, wie man
ECS in realen Aufgaben einsetzt.
Testbedingungen (in der Nähe von echten Spielaufgaben):- Der i5 2500 Prozessor (4 Kerne ohne Hyper-Trading) und Unity2019.3.0f1
- Jedes GameObject jeden Frame ...
A) bewegt sich 10 Minuten lang entlang einer quadratischen Bézier-Kurve vom Startpunkt bis zum Ende.
B) berechnet seinen quadratischen Kollider (Kästchen 10f10f), der math.sincos, math.asin, math.sqrt verwendet (die gleichen, recht komplizierten Berechnungen für alle Tests).
- Objekte vor FPS-Messungen werden an zufälligen Positionen innerhalb der 720fx1280f-Zone festgelegt und an einen zufälligen Punkt in dieser Zone verschoben.
- Alles ist in Release in IL2CPP auf PC getestet
- Die Tests werden einige Sekunden nach dem Start aufgezeichnet, sodass alle anfänglichen vorläufigen Berechnungen und die Einbeziehung von Unity-Systemen den FPS nicht beeinflussen. Aus den gleichen Gründen wird nur der Aktualisierungscode jedes Frames angezeigt.
- Objekte werden in der Version nicht visuell angezeigt, sodass das Rendern keine Auswirkungen auf FPS hat.
Positionen testen und Code aktualisieren
- MonoBehaviour sequentiell (bedingte Markierung).
Das MonoBehaviour-Skript ist an dem Objekt "aufgehängt", bei dessen Aktualisierung die Position, der Collider berechnet und das Selbst bewegt wird.
- Auf Komponentenklassen sequentielle Akteure ohne Parallelisierung.
Code aktualisieren public void Tick(float delta) { foreach (ent entity in groupMoveBezier) { var cMoveBezier = entity.ComponentMoveBezier_noJob(); var cObject = entity.ComponentObject(); ref var obj = ref cObject.obj;
- Schauspieler + Jobs + Burst
Berechnung und Bewegung in Jobs aus Unity.Jobs 0.1.1-, Unity.Burst 1.1.2-Bibliotheken.
Sicherheitschecks - aus
Editor Anhängen - aus
JobsDebbuger - aus
Für den normalen Betrieb von IJobParallelForTransform verfügen alle sich bewegenden Objekte über ein übergeordnetes Objekt (bis zu 255 Objekte in jedem übergeordneten Objekt gemäß der Empfehlung für maximale Leistung).
Code aktualisieren public void Tick(float delta) { if (index <= 0) return; handlePositionUpdate.Complete(); #if UNITY_EDITOR for (var i = 0; i < index; i++) { var obj = nObj[i]; DebugDrowBox(obj.collBox, Color.blue, Time.deltaTime); } #endif jobPositionUpdate.nSetMove = nSetMove; jobPositionUpdate.nObj = nObj; jobPositionUpdate.deltaTime = delta; handlePositionUpdate = jobPositionUpdate.Schedule(transformsAccessArray); } } [BurstCompile] struct JobPositionUpdate : IJobParallelForTransform { public NativeArray<SetMove> nSetMove; public NativeArray<Obj> nObj; [Unity.Collections.ReadOnly] public float deltaTime; public void Execute(int index, TransformAccess transform) { var setMove = nSetMove[index]; var velocityToOneFrame = nSetMove[index].velocityToOneSecond * deltaTime; // setMove.observedDistance += velocityToOneFrame; var t = setMove.observedDistance / setMove.distanceFull; if (t > 1f) t = 1f; var newPos = t.CalculateBesierPos(setMove.posToMove.c0, setMove.posToMove.c2,setMove.posToMove.c1); nSetMove[index] = setMove; // var obj = nObj[index]; obj.properties.c0 = newPos; var posAndSize = new float2x2 { c0 = newPos, c1 = obj.collBox.posAndSize.c1 }; obj.collBox = obj.entity.NewCollBox(posAndSize, new float2(10f, 10f), obj.rotation.ToEulerAnglesZ()); nObj[index] = obj; // transform.position = (Vector2) newPos; } } public struct SetMove { public float2x3 posToMove; public float distanceFull; public float velocityToOneSecond; public float observedDistance; }
- Schauspieler + Parallel
Anstelle der üblichen For-Schleife durch eine Gruppe sich bewegender Entitäten wird Parallel.For aus der System.Threading.Tasks-Bibliothek verwendet. Es berechnet die neue Position und den Collider in parallelen Flüssen. Das Verschieben eines Objekts erfolgt in einer Nachbargruppe.
Code aktualisieren public void Tick(float delta) { Parallel.For(0, groupMoveBezier.length, i => { ref var entity = ref groupMoveBezier[i]; var cMoveBezier = entity.ComponentMoveBezier_actorsParallel(); ref var obj = ref entity.ComponentObject().obj;
Testen mit dem Bewegen [1]:
500 Objekte
(ein Bild aus dem Editor neben dem Text mit FPS, um zu zeigen, was dort visuell passiert)- MonoBehaviour sequentiell:

- Schauspieler nacheinander:

- Schauspieler + Jobs + Burst:

- Schauspieler + Parallel. Für:

5000 Objekte

- MonoBehaviour sequentiell:

- Schauspieler nacheinander:

- Schauspieler + Jobs + Burst:

- Schauspieler + Parallel. Für:

50.000 Objekte

- MonoBehaviour sequentiell:

- Schauspieler nacheinander:

- Schauspieler + Jobs + Burst:

- Schauspieler + Parallel. Für:

Actors + Threaded (integrierte Actors-Parallelisierung in System.Threading)
Schauspieler haben die Fähigkeit, alle Komponenten des Spiels in Strukturen anstatt in Klassen zu halten. Dies sind Hämorrhoiden im Hinblick auf das Schreiben von Code, aber unter solchen Bedingungen arbeitet das Programm eher mit dem Stapel als mit dem verwalteten Haufen, was die Geschwindigkeit seiner Arbeit erheblich beeinflusst.
Code aktualisieren public void Tick(float delta) { groupMoveBezier.Execute(delta); for (int i = 0; i < groupMoveBezier.length; i++) { ref var cObject = ref groupMoveBezier.entities[i].ComponentObject(); cObject.tr.position = new Vector3(cObject.obj.properties.c0.x, cObject.obj.properties.c0.y, 0); #if UNITY_EDITOR DebugDrowBox(cObject.obj.collBox, Color.blue, Time.deltaTime); #endif } } static void HandleCalculation(SegmentGroup segment) { for (int i = segment.indexFrom; i < segment.indexTo; i++) { ref var entity = ref segment.source.entities[i]; ref var cMoveBezier = ref entity.ComponentMoveBezier(); ref var cObject = ref entity.ComponentObject(); ref var obj = ref cObject.obj; // var velocityToOneFrame = cMoveBezier.velocityToOneSecond * segment.delta; cMoveBezier.observedDistance += velocityToOneFrame; var t = cMoveBezier.observedDistance / cMoveBezier.distanceFull; if (t > 1f) t = 1f; var newPos = t.CalculateBesierPos(cMoveBezier.posToMove.c0, cMoveBezier.posToMove.c2, cMoveBezier.posToMove.c1); // obj.properties.c0 = newPos; var posAndSize = new float2x2 { c0 = newPos, c1 = obj.collBox.posAndSize.c1 }; obj.collBox = obj.entity.NewCollBox(posAndSize, new float2(10f, 10f), obj.rotation.ToEulerAnglesZ()); } }
auf Klassenkomponenten

auf Strukturkomponenten

In diesem Fall erhalten wir + 10% FPS, aber im Beispiel gibt es nur zwei Komponentenstrukturen und nicht zehn, wie es im Endprodukt sein sollte. Hier ist ein nichtlineares Wachstum von FPS möglich, da die Komponenten des
Referenztypenprogramms durch
Werttypen ersetzt werden .
Fazit
- In allen Fällen erhöht sich die FPS in Actors without Parallel.For um etwa das Zweifache und damit - im Vergleich zu MonoBehavior sequentiell - um das Dreifache. Mit der Zunahme der mathematischen Berechnungen bleiben diese Anteile erhalten.
- Für mich ist ein zusätzlicher Vorteil von ECS Actors gegenüber MonoBehaviour Sequential, dass die Parallelisierung von Berechnungen, die zur Geschwindigkeit beiträgt, elementar hinzugefügt wird.
- Die Verwendung von Actors + Jobs + Burst erhöht die FPS um etwa das Zehnfache im Vergleich zu MonoBehaviour Sequential
- Zugegebenermaßen ist eine solche Erhöhung der FPS größtenteils auf Burst zurückzuführen. Natürlich müssen Sie für den normalen Betrieb Datentypen von Unity.Mathematics verwenden (ersetzen Sie z. B. Vector3 durch float3).
Und es ist sehr wichtig: auf meinem Prozessor mit 50.000 Objekten auf dem Bildschirm, mit denen sich die FPS erhöhen lassen
vorher
!
Folgende Punkte sind zu beachten:
1) Wenn Sie in den Berechnungen auf eine Bibliothek verzichten können, ist es besser, sie nicht zu verwenden (rote Markierung - schlecht, grün - gut)

2) Sie können die Mathf-Bibliothek nicht verwenden - nur Mathe, sonst kann Burst die Daten nicht vektorisieren und verarbeiten.

- Nach mehreren Tests von Drittanbietern zeigt MonoBehavior Sequential mit 50.000 Objekten überall die gleichen ~ 50 fps. Aber die Arbeit an Actors + Jobs oder Threaded ist sehr unterschiedlich.
Je moderner der Prozessor ist, desto nützlicher ist es, die Arbeit in mehrere in der Warteschlange stehende Jobs aufzuteilen: Positionsberechnung, Kollidierung, Anfahren einer Position.
Sie können ein Testprogramm herunterladen und die Arbeit von Actors + Jobs + Burst [ein Job] mit Actors + Jobs + Burst [vier Jobs] vergleichen. (Auf meinem 4-Core-Prozessor ohne Hyper-Trading ist der erste Test mit 50.000 Objekten -0,2 ms schneller) - Die Wirksamkeit von ECS hängt von der Anzahl der zusätzlichen Elemente ab (Render, Unity-Physik usw.).
[1] Ich weiß nicht, wie die Leistung in anderen ECS-Frameworks in ECS-Unity / DOTS-Systemen ist.Vielen Dank an Oleg Morozov (BenjaminMoore) für die Bearbeitung von Jobs, das Hinzufügen von SceneSelector und eines neuen FPS-Zählers.
Dank an iurii zakipnyi für die Anweisungen, Überarbeitungen und den zusätzlichen Test Actors + Jobs + Burst [vier Job]