Up to date

This page is up to date for Godot 4.2. If you still find outdated information, please open an issue.

CPU-Optimierung

Messen der Performance

Wir müssen wissen, wo die "Bottlenecks" sind, um zu wissen, wie wir unser Programm beschleunigen können. Bottlenecks sind die langsamsten Teile des Programms, die begrenzen, wie schnell das ganze Programm laufen kann. Wenn wir uns auf die Bottlenecks konzentrieren, können wir unseren Aufwand auf die Optimierung der Bereiche konzentrieren, die uns die größte Geschwindigkeitsverbesserung bringen, anstatt viel Zeit mit der Optimierung von Funktionen zu verbringen, die nur zu kleinen Performanceverbesserungen führen.

Für die CPU ist die Verwendung eines Profilers der einfachste Weg, um Bottlenecks zu identifizieren.

CPU-Profiler

Profiler laufen neben Ihrem Programm und nehmen Laufzeit-Messungen vor, um herauszufinden wie viel Zeit in jeder Funktion verbracht wird.

Die Godot-IDE verfügt über einen integrierten Profiler. Er wird nicht jedes Mal ausgeführt wenn Sie Ihr Projekt starten und muss manuell gestartet und gestoppt werden. Dies liegt daran, da das Aufzeichnen dieser Laufzeit-Messungen, wie bei den meisten Profilern, Ihr Projekt erheblich verlangsamen kann.

Wenn der Profiler seine Arbeit getan hat, können Sie sich die Ergebnisse eines Frames ansehen.

Screenshot des Godot-Profilers

Dies sind die Profiling-Ergebnisse eines der Demo-Projekte.

Bemerkung

Wir können die Kosten für Built-in-Prozesse wie Physik und Audio sowie die Kosten für unsere eigenen Skriptfunktionen unten sehen.

Die Wartezeit auf verschiedene Built-in-Server wird in den Profilern möglicherweise nicht berücksichtigt. Dies ist ein bekannter Bug.

Wenn ein Projekt langsam läuft, werden Sie häufig feststellen, dass eine bestimmte Funktion oder ein Prozess viel mehr Zeit benötigt als andere. Dies ist Ihr primäres Bottleneck und Sie können die Geschwindigkeit normalerweise erhöhen, indem Sie diesen Bereich optimieren.

Weitere Informationen zur Verwendung des eingebauten Profilers finden sich unter Debugger-Panel.

Externe Profiler

Obwohl der Godot-IDE-Profiler sehr praktisch und nützlich ist, benötigen Sie manchmal mehr Leistung und die Möglichkeit, für den Quellcode der Godot-Engine selbst ein Profiling-Lauf durchzuführen.

Sie können eine Reihe von C++-Profilern von Drittanbietern verwenden, um dies zu tun.

Screenshot von Callgrind

Beispielergebnisse von Callgrind, das Teil von Valgrind ist.

Von links beginnend listet Callgrind folgende Werte auf: Den Prozentsatz der Zeit innerhalb einer Funktion und ihrer untergeordneten Elemente (einschließlich), den Prozentsatz der Zeit innerhalb der Funktion selbst, ohne untergeordnete Funktionen (Self), die Häufigkeit der Funktionsaufrufe, den Funktionsnamen und die Datei oder das Modul.

In diesem Beispiel können wir sehen, dass fast die gesamte Zeit unter der Funktion Main::iteration() verbracht wird. Dies ist die Hauptfunktion im Godot-Quellcode, die wiederholt aufgerufen wird. Sie sorgt dafür, dass Frames gezeichnet werden, Physik-Ticks simuliert und Nodes und Skripte aktualisiert werden. Ein großer Teil der Zeit entfällt auf die Funktionen zum Rendern eines Canvas (66%), da in diesem Beispiel ein 2D-Benchmark verwendet wird. Darunter sehen wir, dass fast 50% der Zeit außerhalb des Godot-Codes in libglapi und i965_dri (dem Grafiktreiber) verbracht wird. Dies zeigt uns, dass ein großer Teil der CPU-Zeit im Grafiktreiber verbracht wird.

Dies ist tatsächlich ein hervorragendes Beispiel, denn in einer idealen Welt würde nur ein sehr geringer Teil der Zeit im Grafiktreiber verbracht werden. Dies ist ein Hinweis darauf, dass es ein Problem damit gibt, dass zu viel Kommunikation und Arbeit in der Grafik-API stattfindet. Dieses spezifische Profiling führte zur Entwicklung von 2D-Batching, was zu einer erheblichen Beschleunigung des 2D-Renderings führt, indem Bottlecks in diesem Bereich reduziert wurden.

Manuelle Timing-Funktionen

Eine weitere praktische Methode, insbesondere wenn Sie das Bottleneck mithilfe eines Profilers identifiziert haben, besteht darin, die Funktion oder den zu testenden Bereich manuell zu messen. Die Einzelheiten variieren je nach Sprache, aber in GDScript würden Sie Folgendes tun:

var time_start = Time.get_ticks_usec()

# Your function you want to time
update_enemies()

var time_end = Time.get_ticks_usec()
print("update_enemies() took %d microseconds" % time_end - time_start)

Bei der manuellen Laufzeitmessung von Funktionen ist es in der Regel eine gute Idee, die Funktion mehrmals (1.000 Mal oder öfter) auszuführen, anstatt nur einmal (es sei denn, es handelt sich um eine sehr langsame Funktion). Der Grund dafür ist, dass Laufzeitmesser oft eine begrenzte Genauigkeit haben. Außerdem planen CPUs ihre Prozesse willkürlich. Daher ist ein Durchschnitt über eine Reihe von Läufen genauer als eine einzelne Messung.

Wenn Sie versuchen, Funktionen zu optimieren, achten Sie darauf, entweder wiederholt Profilings zu erstellen oder die Laufzeit zu messen, während Sie arbeiten. So erhalten Sie eine wichtige Rückmeldung darüber, ob die Optimierung funktioniert (oder nicht).

Caches

CPU-Caches sind ein weiterer Punkt, der besonders zu beachten ist, vor allem, wenn man die Laufzeit-Ergebnisse zweier verschiedener Versionen einer Funktion vergleicht. Die Ergebnisse können stark davon abhängen, ob sich die Daten im CPU-Cache befinden oder nicht. CPUs laden keine Daten direkt aus dem System-RAM, auch wenn dieser im Vergleich zum CPU-Cache riesig ist (mehrere Gigabyte statt einiger Megabyte). Das liegt daran, dass der Zugriff auf das System-RAM sehr langsam ist. Stattdessen laden die CPUs Daten aus einem kleineren, schnelleren Speicherbereich, dem Cache. Das Laden von Daten aus dem Cache ist sehr schnell, aber jedes Mal, wenn Sie versuchen, eine Speicheradresse zu laden, die nicht im Cache gespeichert ist, muss der Cache eine Reise zum Hauptspeicher machen und langsam Daten laden. Diese Verzögerung kann dazu führen, dass die CPU lange Zeit untätig bleibt, was als "Cache-Miss" bezeichnet wird.

Das bedeutet, dass eine Funktion beim ersten Mal möglicherweise langsam ausgeführt wird, weil sich die Daten nicht im CPU-Cache befinden. Beim zweiten Mal und später kann sie viel schneller ausgeführt werden, da sich die Daten im Cache befinden. Aus diesem Grund sollten Sie bei der Laufzeitmessung immer Durchschnittswerte verwenden und sich der Auswirkungen des Cache bewusst sein.

Das Verständnis des Caching ist auch für die CPU-Optimierung von entscheidender Bedeutung. Wenn Sie einen Algorithmus (eine Routine) haben, der kleine Datenbits aus zufällig verteilten Bereichen des Hauptspeichers lädt, kann dies zu vielen Cache-Misses führen. In vielen Fällen wartet die CPU auf Daten, anstatt irgendeine Arbeit zu erledigen. Wenn Sie stattdessen Ihre Datenzugriffe lokal halten oder noch besser linear auf den Speicher zugreifen können (wie eine fortlaufende Liste), funktioniert der Cache optimal und die CPU kann so schnell wie möglich arbeiten.

Godot kümmert sich normalerweise um solche Low-Level-Details für Sie. Zum Beispiel sorgen die Server-APIs dafür, dass Daten bereits für das Zwischenspeichern von Dingen wie Rendering und Physik optimiert sind. Dennoch sollten Sie beim Schreiben von GDExtensions besonders auf das Caching achten.

Sprachen

Godot unterstützt eine Reihe verschiedener Sprachen und es ist zu beachten, dass es manchmal Kompromisse braucht. Einige Sprachen sind auf Benutzerfreundlichkeit ausgelegt, was auf Kosten der Geschwindigkeit geht, und andere sind schneller, aber dafür schwieriger zu handhaben.

Die integrierten Engine-Funktionen werden unabhängig von der von Ihnen gewählten Skriptsprache mit derselben Geschwindigkeit ausgeführt. Wenn Ihr Projekt viele Berechnungen in seinem eigenen Code durchführt, sollten Sie diese Berechnungen in eine schnellere Sprache verschieben.

GDScript

GDScript ist so konzipiert, dass es einfach zu benutzen und zu überarbeiten ist, und ist ideal für die Entwicklung vieler Arten von Spielen. Allerdings wird bei dieser Sprache die Benutzerfreundlichkeit höher bewertet als die Performance. Wenn Sie umfangreiche Berechnungen durchführen müssen, sollten Sie einen Teil Ihres Projekts in eine der anderen Sprachen verlagern.

C#

C# ist beliebt und wird von Godot erstklassig unterstützt. Es bietet einen guten Kompromiss zwischen Geschwindigkeit und Benutzerfreundlichkeit. Achten Sie jedoch auf mögliche Pausen bei der Garbage Collection und Lecks, die während des Spielens auftreten können. Ein gängiger Ansatz zur Umgehung von Problemen mit der Garbage Collection ist die Verwendung von Object Pooling, was jedoch außerhalb des Rahmens dieses Handbuchs liegt.

Andere Sprachen

Drittanbieter bieten Unterstützung für verschiedene andere Sprachen, darunter Rust.

C++

Godot ist in C++ geschrieben. Die Verwendung von C++ führt in der Regel zu dem schnellsten Code. In der Praxis ist es jedoch am schwierigsten, ihn auf den Rechnern der Endbenutzer auf verschiedenen Plattformen einzusetzen. Optionen für die Verwendung von C++ sind GDExtensions und custom modules.

Threads

Erwägen Sie die Verwendung von Threads, wenn Sie viele Berechnungen durchführen, die parallel zueinander laufen können. Moderne CPUs haben mehrere Kerne, von denen jeder eine begrenzte Menge an Arbeit erledigen kann. Indem Sie die Arbeit auf mehrere Threads verteilen, können Sie die CPU-Effizienz weiter steigern.

Der Nachteil von Threads ist, dass man sehr vorsichtig sein muss. Da jeder CPU-Kern unabhängig arbeitet, kann es passieren, dass sie versuchen, gleichzeitig auf denselben Speicher zuzugreifen. Ein Thread kann auf eine Variable zugreifen, während ein anderer schreibt: Dies wird als Race Condition bezeichnet. Bevor Sie Threads verwenden, sollten Sie sich über die Gefahren im Klaren sein und wissen, wie Sie diese Race Conditions verhindern können. Threads können das Debuggen erheblich erschweren.

Für weitere Informationen zu Threads siehe Mehrere Threads verwenden.

SceneTree

Obwohl Nodes ein unglaublich mächtiges und vielseitiges Konzept sind, sollte man sich darüber im Klaren sein, dass jeder Node Kosten verursacht. Built-in-Funktionen wie _process() und _physics_process() pflanzen sich durch den Baum fort. Dieses "Housekeeping" kann die Performance verringern, wenn Sie eine sehr große Anzahl von Nodes haben (wie viele genau, hängt von der Zielplattform ab und kann von Tausenden bis zu Zehntausenden reichen, also stellen Sie sicher, dass Sie während der Entwicklung ein Performance-Profiling auf allen Zielplattformen erstellen).

Jeder Node wird im Godot-Renderer einzeln behandelt. Daher kann eine geringere Anzahl von Nodes mit jeweils Inhalt zu einer besseren Performance führen.

Eine Besonderheit des SceneTree ist, dass man manchmal eine viel bessere Performance erhält, wenn man Nodes aus dem SceneTree entfernt, anstatt sie anzuhalten oder zu verstecken. Sie müssen einen abgetrennten Node nicht löschen. Man kann zum Beispiel einen Verweis auf einen Node behalten, ihn mit Node.remove_child(node) aus dem SceneTree entfernen und ihn später mit Node.add_child(node) wieder anhängen. Dies kann sehr nützlich sein, um z.B. Bereiche in einem Spiel hinzuzufügen oder zu entfernen.

Sie können den Szenenbaum mithilfe von Server-APIs vollständig umgehen. Weitere Informationen finden Sie unter Optimierung mit Servern.

Physik

In einigen Situationen kann die Physik zu einem Bottleneck werden, insbesondere bei komplexen Welten und einer großen Anzahl von Physikobjekten.

Einige Techniken zur Beschleunigung der Physik:

  • Versuchen Sie vereinfachte Versionen Ihrer gerenderten Geometrie für die Physik zu verwenden. Dies ist für Endbenutzer nicht wahrnehmbar, kann jedoch die Performance erheblich steigern.

  • Versuchen Sie Objekte aus der Physik zu entfernen, wenn sie sich außerhalb des aktuellen Bereichs befinden, oder Physikobjekte wiederzuverwenden (möglicherweise erlauben Sie beispielsweise 8 Monster pro Bereich und verwenden diese wieder).

Ein weiterer entscheidender Aspekt von Physik ist die Physik-Tick-Rate. In einigen Spielen können Sie die Tick-Rate erheblich reduzieren. Anstatt beispielsweise die Physik 60 Mal pro Sekunde zu aktualisieren, können Sie sie mit 30 oder sogar 20 Ticks pro Sekunde aktualisieren. Dies kann die CPU-Auslastung erheblich reduzieren.

Der Nachteil einer Änderung der Physik-Tick-Rate ist, dass es zu ruckartigen Bewegungen oder Jittern kommen kann, wenn die Aktualisierungsrate der Physik nicht mit den gerenderten Bildern pro Sekunde übereinstimmt. Außerdem erhöht sich durch die Verringerung der Physik-Tick-Rate die Eingabeverzögerung. Es wird empfohlen, die Default-Physik-Tick-Rate (60 Hz) in den meisten Spielen mit Echtzeit-Spielerbewegungen beizubehalten.

Die Lösung für Jitter ist die Verwendung von fixed timestep interpolation, bei der die gerenderten Positionen und Rotationen über mehrere Frames geglättet werden, um der Physik zu entsprechen. Sie können dies entweder selbst implementieren oder ein Addon eines Drittanbieters verwenden. In Bezug auf die Performance ist die Interpolation eine sehr billige Operation im Vergleich zur Ausführung eines Physik-Ticks. Sie ist um Größenordnungen schneller, so dass dies ein erheblicher Performance-Gewinn sein kann, während gleichzeitig Jitter reduziert wird.