Godot CPU-Rendering-Optimierung: Wie Profiling zwei Engpässe in einen deutlichen Leistungsschub verwandelte
Public- 15 Sep, 2026

Von außen wirkt Rendering-Optimierung oft wie schwarze Magie, doch der eigentliche Prozess ist weit methodischer, als es scheint. Die Entwickler von Godot haben detailliert erläutert, wie sie den CPU-Code im Renderer der Engine optimieren, anhand konkreter Beispiele aus dem vergangenen Jahr. Die gesamte Arbeit beruht auf einem zentralen Prinzip: Jede Engine muss die Last zwischen CPU und GPU ausbalancieren, denn die endgültige Leistung wird immer durch die langsamere der beiden bestimmt. Keine noch so gute CPU-Abstimmung kann ineffiziente Shader ausgleichen. Das Engine-Team geht ständig solche Kompromisse ein — zum Beispiel verschlechtert 2D-Batching die GPU-Leistung geringfügig, verbessert dafür aber die CPU-Leistung erheblich, und da 2D-Spiele meist zuerst an CPU-Grenzen stoßen, ist Batching fast immer ein klarer Gewinn. 3D-Spiele sind dagegen in der Regel GPU-limitiert, weshalb Godot Occlusion Culling auf der CPU ausführt, um die GPU zu entlasten. Zudem spart jede Optimierung Akkulaufzeit auf Plattformen mit begrenzter Stromversorgung.
Der grundlegende Zyklus der CPU-Optimierung ist einfach: den Engpass finden, verstehen, wie das System funktioniert, eine Änderung vornehmen und erneut messen. Erneutes Messen ist wichtig, weil Optimierungen sich oft unerwartet verhalten. Eine Verzweigung innerhalb einer Schleife einzufügen, um überflüssige Arbeit zu überspringen, erscheint schneller, kann aber die Vektorisierung des Compilers blockieren und alles verlangsamen. Ebenso kann das Caching eines Werts, um redundante Berechnungen zu vermeiden, den gegenteiligen Effekt haben, wenn der eigentliche Engpass das Lesen aus dem Speicher ist: In diesem Fall wäre es schneller, die Arbeit zweimal zu erledigen. Den Schuldigen zu finden ist der schwierigste Schritt, und er beginnt mit einem Profiler. In Godot sind zwei eingebaut: einer für GDScript, ein anderer für den Renderer. Für den Engine-Code selbst benötigen Entwickler jedoch ein externes Werkzeug. Im Artikel wird Superluminal verwendet — ein Sampling-Profiler, der sich mit einer laufenden Godot-Instanz verbindet, Daten während des Engine-Betriebs aufzeichnet, zeigt, welcher Code wann ausgeführt wird, und kürzlich Linux-Unterstützung erhalten hat. Godot unterstützt außerdem das Bauen mit Tracy für Tracing-basiertes Profiling.
Das erste Beispiel stammte aus einem Gespräch mit einem Entwickler des Spiels Heidi's Legacy: Mountains Calling, der festgestellt hatte, dass der Animationscode in Godot so langsam ist, dass der Inhalt auf rund zwanzig animierte Charaktere begrenzt werden musste. Der Aufbau umfasste einen AnimationPlayer, der die Vertices eines Polygon2D animierte, das auf einem Viewport über einem Sprite3D angezeigt wurde — eine ungewöhnliche, aber an sich nicht teure Technik. Das Profiling einer minimalen Reproduktion (getestet auf einem Ryzen 5 9600X) zeigte sofort den Übeltäter. Die Klasse Polygon2D verbrachte fast die gesamte Zeit damit, in jedem Frame die bisherige Mesh-Oberfläche freizugeben und eine neue zuzuweisen, da sich die Vertex-Daten auf der CPU jeden Frame ändern. Der Aufrufgraph und die Ansicht „Source and Disassembly“ wiesen auf die exakten Zeilen hin, die das interne Mesh neu aufbauen, und bestätigten das unnötige Gehäuse.
Die Lösung nutzte eine Low-Level-API von Godot zum manuellen Aktualisieren der Vertex-Daten an Ort und Stelle. Wenn die Vertex-Anzahl konstant bleibt, muss das Mesh nicht freigegeben und neu zugewiesen werden — man kann die neuen Vertex-Daten einfach in das bestehende Mesh hochladen. Der größte Teil des Pull-Requests bestand darin zu entscheiden, welche Fälle für Aktualisierungen an Ort und Stelle sicher sind und welche wirklich neu erstellt werden müssen. Dadurch stieg die Leistung animierter Polygon2D etwa um das Dreifache: Die Frame-Zeit sank von 35 ms auf 13 ms, und die Szene stieg von 28 FPS auf 83 FPS. Nach der Änderung entfielen etwa 50 % der Frame-Zeit auf das Rendering, etwa 30 % auf die Animation und 20 % auf das Polygon2D-Update. Der Autor merkte an, dass weitere Gewinne möglich sind — nur die geänderten Mesh-Bereiche statt des gesamten Mesh erneut hochzuladen —, aber solche partiellen Aktualisierungen erfordern einen echten Testfall, der sie leitet, und da dieses Spiel fast jeden Vertex in jedem Frame animiert, würde das Verfolgen partieller Änderungen höchstwahrscheinlich einen Netto-Nachteil bedeuten.
Der zweite Fall betraf die D3D12-Shader-Kompilierungspipeline in Godot, die nach Beschwerden über langsames Laden in Angriff genommen wurde. Der langsamste Schritt der Pipeline war die Transpilierung, und der Wechsel von Vulkan zu DXIL brachte eine spürbare Einbuße bei der Ladezeit mit sich, da Vulkan SPIR-V direkt verarbeiten kann. Die Ablaufverfolgung in Superluminal zeigte den Grund: dichtes Multithreading mit großen Lücken, in denen nur ein paar Threads aktiv waren. Beim Heranzoomen zeigte sich, dass die Lücken entstanden, wenn alle Threads gleichzeitig mehr Heap-Speicher vom Betriebssystem anforderten — sie standen still und warteten auf die Zuweisung aus einem einzigen globalen Heap. Die Lösung gab jedem Thread einen eigenen Heap statt ständiger Zugriffe auf den gemeinsamen globalen Heap und beseitigte so die Wartezeiten. Das sparte 11 Sekunden Ladezeit in der TPS-Demo — Zeit, in der die CPU vollständig im Leerlauf war. Beide Beispiele unterstreichen dieselbe Lektion: Mit dem richtigen Werkzeug wird die Lösung offensichtlich, sobald das Problem richtig erkannt ist.