Wir stellen unser neues E-Book vor: Unitys Data-Oriented Technology Stack (DOTS) für fortgeschrittene Entwickler

THOMAS KROGH-JACOBSEN / UNITY TECHNOLOGIESSenior Technical Content Marketing Manager
May 30, 2024|9 Min.
Wir stellen unser neues E-Book vor: Unitys Data-Oriented Technology Stack (DOTS) für fortgeschrittene Entwickler
Diese Website wurde aus praktischen Gründen für Sie maschinell übersetzt. Die Richtigkeit und Zuverlässigkeit des übersetzten Inhalts kann von uns nicht gewährleistet werden. Sollten Sie Zweifel an der Richtigkeit des übersetzten Inhalts haben, schauen Sie sich bitte die offizielle englische Version der Website an.

Mit dem Data-Oriented Technology Stack (DOTS) von Unitykönnen Sie komplexe Spiele im großen Maßstab erstellen. Er bietet eine Reihe leistungssteigernder Tools, mit denen Sie das Beste aus Ihrer Zielhardware herausholen können.

Dieses über 50 Seiten umfassende E-Book „Einführung in den datenorientierten Technologie-Stack für fortgeschrittene Unity-Entwickler“ steht jetzt zum kostenlosen Download bereit. Verwenden Sie es als Einführung, um die datenorientierte Programmierung besser zu verstehen und zu beurteilen, ob DOTS die richtige Wahl für Ihr nächstes Projekt ist. Egal, ob Sie ein neues DOTS-basiertes Projekt starten oder DOTS für leistungskritische Teile Ihres auf Monoverhalten basierenden Spiels implementieren möchten, dieser Leitfaden deckt alle notwendigen Grundlagen auf strukturierte und klare Weise ab.

Da Unity 6 in der Vorschau verfügbar ist und DOTS 1.0 produktionsreif ist, ist dies ein guter Zeitpunkt, die Möglichkeiten zu erkunden, die DOTS bietet. Das E-Book wurde von Brian Will, einem leitenden Softwareentwickler bei Unity, verfasst und ergänzt die aktualisierten Unity Learn-Beispiele, das aktuelle DOTS-Bootcamp und die GitHub-Beispiele in der Ressourcensammlung für Entwickler, die die Arbeit mit DOTS erlernen möchten.

Ein Leitfaden, der Ihnen bei der Entscheidung hilft, ob DOTS die richtige Wahl für Ihr Spiel ist
Im Entity Component System von Unity werden alle Entitäten mit demselben Satz von Komponententypen zusammen im selben „Archetyp“ gespeichert.

Mit diesem E-Book möchten wir Ihnen dabei helfen, eine fundierte Entscheidung darüber zu treffen, ob die Implementierung einiger oder aller DOTS-Pakete und -Technologien die richtige Entscheidung für Ihr bestehendes oder zukünftiges Unity-Projekt ist. Jeder Teil des Stapels spielt eine Rolle bei der Verbesserung der Ausführungsgeschwindigkeit und Effizienz eines Spiels. Das Handbuch soll jeden dieser Teile erklären, wie sie zusammen verwendet werden können und ihre gemeinsame Grundlage, das Unity Entity Component System (ECS), bilden.

Ein Hauptgrund für die Verwendung von DOTS besteht darin, die maximale Leistung aus Ihrer Zielhardware herauszuholen. Dazu sind Kenntnisse in den Bereichen Multithreading und Speicherzuweisung erforderlich. Um DOTS optimal nutzen zu können, müssen Sie Ihren datenorientierten Code und Ihre datenorientierten Projekte außerdem anders strukturieren als Ihre C#-basierten Monobehaviour-Projekte mit ihrem höheren Abstraktionsgrad.

Schauen wir uns genauer an, was Sie im E-Book finden.

CTA: Laden Sie die Einführung in den datenorientierten Technologie-Stack für fortgeschrittene Unity-Entwickler herunter.

Was steht im DOTS E-Book?
 Eine Szene aus dem Feuerwehr-Beispiel, verfügbar im EntityComponentSystemSamples Github

Der erste Abschnitt des Handbuchs, den wir unten eingefügt haben, stellt einige der Faktoren vor, die zu einer schlechten CPU-Leistung in einem Spiel beitragen können, wie z. B. der Aufwand für die Garbage Collection, nicht cachefreundliche Daten und Code, suboptimaler, vom Compiler generierter Maschinencode und mehr.

Im nächsten Abschnitt wird erläutert, wie die einzelnen DOTS-Pakete und -Funktionen das Schreiben von Code erleichtern, der CPU-Leistungseinbußen vermeidet. Sie finden hilfreiche Erklärungen zu:

  • C#-Jobsystem
  • Burst-Compiler
  • Sammlungen
  • Mathematik
  • Entitäten
  • Entitäten Grafiken
  • Einheitsphysik
  • Netcode für Entitäten

Nach einem Überblick über jeden Teil des Stapels erhalten Sie eine Einführung in das GitHub-Repository „ EntityComponentSystemSamples“ , das viele Beispiele enthält, die sowohl grundlegende als auch erweiterte DOTS-Funktionen vorstellen. Einige der Beispiele im Github-Repository werden in einem neuen Unity Learn-Kurs zu DOTS, „Machen Sie sich mit DOTS vertraut“,reproduziert.

Der andere wichtige Abschnitt des DOTS-Handbuchs ist der Anhang. Hier liefert Brian Will detaillierte Erklärungen zu Konzepten im Zusammenhang mit Unity ECS, darunter Speicherzuweisung und Garbage Collection, Speicher- und CPU-Cache, Multithread-Programmierung, die Einschränkungen der objektorientierten Programmierung und datenorientierte Programmierung.

Excerpt: Über die Leistung
Ein Profil aus dem Unity Profiler, das Burst-kompilierte Jobs zeigt, die das Potenzial der CPU nutzen und über viele Arbeitsthreads ausgeführt werden.

Wenn Sie ein erfahrener Spieleentwickler sind, wissen Sie, dass die Leistungsoptimierung auf Zielplattformen eine Aufgabe ist , die sich durch den gesamten Entwicklungszyklus zieht . Vielleicht läuft Ihr Spiel auf einem High-End-PC gut, aber was ist mit den Low-End-Mobilplattformen, die Sie ebenfalls anstreben? Dauern die Frames deutlich länger als andere, sodass es zu spürbaren Verzögerungen kommt? Sind die Ladezeiten nervig lang und friert das Spiel jedes Mal für ganze Sekunden ein, wenn der Spieler durch eine Tür geht? In einem solchen Szenario ist nicht nur die aktuelle Erfahrung unterdurchschnittlich, sondern Sie werden auch effektiv daran gehindert, weitere Funktionen hinzuzufügen: Mehr Details und Maßstäbe der Umgebung, Mechaniken, Charaktere und Verhaltensweisen, Physik und Plattformen.

Wer ist der Schuldige? In vielen Projekten wird Folgendes gerendert: Texturen sind zu groß, Meshes zu komplex, Shader zu teuer oder Batching, Culling und LOD werden ineffektiv eingesetzt.

Eine weitere häufige Falle ist die übermäßige Verwendung komplexer Mesh-Collider, die die Kosten der Physiksimulation erhöhen. Oder die Spielsimulation selbst ist langsam. Der von Ihnen geschriebene C#-Code, der definiert, was Ihr Spiel einzigartig macht, benötigt möglicherweise zu viele Millisekunden CPU-Zeit pro Frame.

Wie schreibt man also einen Spielcode, der schnell oder zumindest nicht langsam ist?

In früheren Jahrzehnten konnten PC-Spieleentwickler dieses Problem oft lösen, indem sie einfach abwarteten. Von den 1970er Jahren bis ins 21. Jahrhundert verdoppelte sich die Leistung einzelner CPU-Threads im Allgemeinen alle paar Jahre (ein Phänomen, das als Mooresches Gesetzbekannt ist), sodass ein PC-Spiel im Laufe seines Lebenszyklus „wie durch Zauberhand“ schneller wurde. Allerdings fielen in den letzten beiden Jahrzehnten die Leistungssteigerungen bei CPU-Single-Thread-Prozessoren relativ bescheiden aus. Stattdessen ist die Anzahl der Kerne in der CPU gestiegen und selbst kleine Handheld-Geräte wie Smartphones verfügen heute über mehrere Kerne. Darüber hinaus ist die Kluft zwischen High-End- und Low-End-Gaming-Geräten größer geworden, da ein großer Teil der Spielerbasis Hardware verwendet, die mehrere Jahre alt ist. Auf schnellere Hardware zu warten, scheint keine praktikable Strategie mehr zu sein.

Die Frage ist also: „Warum ist mein CPU-Code überhaupt langsam?“ Es gibt mehrere häufige Fallstricke:

  • Die Garbage Collection verursacht spürbaren Mehraufwand und Pausen: Dies liegt daran, dass der Garbage Collector als automatischer Speichermanager fungiert, der die Zuweisung und Freigabe von Speicher für eine Anwendung verwaltet. Durch die Garbage Collection wird nicht nur die CPU- und Speicherlast erhöht, sondern die Ausführung Ihres Codes wird manchmal für mehrere Millisekunden unterbrochen. Benutzer könnten diese Pausen als kleine Ruckler oder als störenderes Stottern empfinden.
  • Der vom Compiler generierte Maschinencode ist nicht optimal: Einige Compiler generieren deutlich weniger optimierten Code als andere, wobei die Ergebnisse je nach Plattform unterschiedlich ausfallen.
  • Die CPU-Kerne werden unzureichend ausgelastet: Obwohl die leistungsschwächsten Geräte von heute über Mehrkern-CPUs verfügen, belassen viele Spiele den Großteil ihrer Logik einfach auf dem Haupt-Thread, da das Schreiben von Multithread-Code oft schwierig und fehleranfällig ist.
  • Die Daten sind nicht cachefreundlich: Der Zugriff auf Daten aus dem Cache ist viel schneller als das Abrufen aus dem Hauptspeicher. Der Zugriff auf den Systemspeicher kann jedoch erfordern, dass die CPU Hunderte von CPU-Zyklen wartet. Stattdessen soll die CPU möglichst viele Daten aus ihrem Cache lesen und schreiben. Am einfachsten lässt sich dies erreichen, indem der Speicher sequenziell gelesen und geschrieben wird. Die cachefreundlichste Art der Datenspeicherung besteht daher in dicht gepackten, zusammenhängenden Arrays. Wenn Ihre Daten hingegen nicht zusammenhängend im gesamten Speicher verstreut sind, führt der Zugriff darauf in der Regel zu vielen teuren Cache-Fehlern; die CPU fordert Daten an, die nicht im Cache-Speicher vorhanden sind, und muss sie stattdessen aus dem langsameren Hauptspeicher abrufen.
  • Der Code ist nicht cachefreundlich: Wenn Code ausgeführt wird, muss er aus dem Systemspeicher geladen werden, sofern er sich nicht bereits im Cache befindet. Eine Strategie besteht darin, eine Funktion vorzugswürdigerweise an so wenigen Stellen wie möglich aufzurufen, um die Häufigkeit des Ladens aus dem Systemspeicher zu verringern. Anstatt beispielsweise eine bestimmte Funktion an verschiedenen Stellen im Frame aufzurufen, ist es besser, sie in einer einzigen Schleife aufzurufen, sodass der Code höchstens einmal pro Frame geladen werden muss.
  • Der Code ist übermäßig abstrahiert: Neben anderen Problemen führt Abstraktion tendenziell zu einer Komplexität von Daten und Code, was die zuvor genannten Probleme verschärft: Die Verwaltung von Zuordnungen ohne Garbage Collection wird schwieriger; der Compiler ist möglicherweise nicht in der Lage, die Optimierung so effektiv durchzuführen; sicheres und effizientes Multithreading wird schwieriger und Ihre Daten und Ihr Code werden tendenziell weniger cachefreundlich. Hinzu kommt, dass Abstraktionen dazu neigen, die Leistungskosten zu verteilen, sodass der gesamte Code langsamer ist und keine klaren Engpässe zur Optimierung entstehen.

Alle oben genannten Probleme treten häufig in Unity-Projekten auf. Schauen wir uns diese genauer an:

  • Obwohl Sie in C# manuell zugewiesene Objekte erstellen können (also Objekte, die nicht der Garbage Collection unterliegen), besteht die Standardnorm in C# und den meisten Unity-Projekten darin, C#-Klasseninstanzenzu verwenden, die der Garbage Collection unterliegen. In der Praxis haben Unity-Benutzer dieses Problem schon lange mit einer Technik namens „Pooling“ abgemildert (obwohl Pooling wohl den Zweck der Verwendung einer Sprache mit Garbage Collection von vornherein zunichte macht). Der Hauptvorteil des Objekt-Poolings liegt in der effizienten Wiederverwendung von Objekten aus einem vorab zugewiesenen Pool, wodurch die häufige Erstellung und Freigabe von Objekten entfällt.
  • Im Unity Editor wird C#-Code normalerweise in Maschinencode kompiliert mit dem Mono-Compiler. Bei eigenständigen Builds können Sie mit IL2CPP (C# Intermediate Language, plattformübergreifend kompiliert mit C++) bessere Ergebnisse erzielen. Dies bringt jedoch auch einige Nachteile mit sich, beispielsweise längere Build-Zeiten und eine erschwerte Mod-Unterstützung .
  • Es kommt häufig vor, dass Unity-Projekte ihren gesamten Code im Hauptthread ausführen, teilweise weil Unity dies vereinfacht:
  • Die Unity-Ereignisfunktionen, wie etwa die Update()-Methode von MonoBehaviours, werden alle auf dem Hauptthread ausgeführt.
  • Die meisten Unity-APIs können nur sicher vom Hauptthread aus aufgerufen werden.
  • Die Daten in einem typischen Unity-Projekt sind in der Regel als eine Reihe zufälliger Objekte strukturiert, die über den gesamten Speicher verstreut sind, was zu einer schlechten Cache-Auslastung führt. Auch dies liegt teilweise daran, dass Unity dies vereinfacht:
  • Ein GameObject und seine Komponenten werden alle separat zugewiesen, sodass sie häufig in unterschiedlichen Teilen des Speichers landen.
  • Der Code in einem typischen Unity-Projekt ist in der Regel nicht cachefreundlich:
  • Herkömmliche C#- und Unity-APIs fördern einen objektorientierten Codestil, der zu zahlreichen kleinen Methoden und komplexen Aufrufketten tendiert. Im Gegensatz zu einem datenorientierten Ansatz ist es nicht sehr hardwarefreundlich.
  • Die Ereignisfunktionen jedes MonoBehaviour werden einzeln aufgerufen und die Aufrufe sind nicht unbedingt nach MonoBehaviour-Typ gruppiert. Wenn Sie beispielsweise 1.000 Monster- Monoverhalten haben, wird jedes Monster separat und nicht unbedingt zusammen mit den anderen Monstern aktualisiert.

Der objektorientierte Stil des herkömmlichen C# und vieler Unity-APIs führt im Allgemeinen zu abstraktionslastigen Lösungen. Der resultierende Code weist dann überall Ineffizienzen auf, die nur schwer zu erkennen und zu isolieren sind.

Für wen ist das DOTS-E-Book?
Eine Vorschau auf das neue E-Book „Einführung in den datenorientierten Technologie-Stack für fortgeschrittene Unity-Entwickler“.

Dieses E-Book ist für jeden kostenlos erhältlich, ist jedoch auf Unity-Entwickler zugeschnitten, die Erfahrung mit monoverhaltensbasierter, objektorientierter Spieleentwicklung haben, aber neu bei Unity DOTS und datenorientierter Designentwicklung sind.

Wir hoffen, dass Ihnen der Leitfaden dabei hilft, DOTS zu verstehen und wie diese Funktionen Ihrem nächsten Unity-Projekt zugute kommen können. Außerdem hoffen wir, dass er es Ihnen erleichtert, den vollen Nutzen aus den in unserem GitHub-Repository verfügbaren Beispielen zu ziehen.