CPU-Optimierungen

Leistung messen

We have to know where the "bottlenecks" are to know how to speed up our program. Bottlenecks are the slowest parts of the program that limit the rate that everything can progress. Focussing on bottlenecks allows us to concentrate our efforts on optimizing the areas which will give us the greatest speed improvement, instead of spending a lot of time optimizing functions that will lead to small performance improvements.

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

CPU Profiler

Profiler laufen neben Ihrem Programm und nehmen Timing-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 Zeit-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.

../../_images/godot_profiler.png
Screenshot of the Godot profiler

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

Bemerkung

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

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

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

Weitere Informationen zur Verwendung des Profilers in Godot finden Sie 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 den Quellcode der Godot-Engine selbst zu untersuchen.

Sie können dazu eine Reihe von Profilern von Drittanbietern verwenden, darunter Valgrind, VerySleepy, HotSpot, Visual Studio und Intel VTune.

Bemerkung

You will need to compile Godot from source to use a third-party profiler. This is required to obtain debugging symbols. You can also use a debug build, however, note that the results of profiling a debug build will be different to a release build, because debug builds are less optimized. Bottlenecks are often in a different place in debug builds, so you should profile release builds whenever possible.

Screenshot of Callgrind

Dies sind Beispielergebnisse von Callgrind, einem Teil von Valgrind, unter Linux.

Von links beginnend listet Callgrind 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 this example, we can see nearly all time is spent under the Main::iteration() function. This is the master function in the Godot source code that is called repeatedly. It causes frames to be drawn, physics ticks to be simulated, and nodes and scripts to be updated. A large proportion of the time is spent in the functions to render a canvas (66%), because this example uses a 2D benchmark. Below this, we see that almost 50% of the time is spent outside Godot code in libglapi and i965_dri (the graphics driver). This tells us the a large proportion of CPU time is being spent in the graphics driver.

This is actually an excellent example because, in an ideal world, only a very small proportion of time would be spent in the graphics driver. This is an indication that there is a problem with too much communication and work being done in the graphics API. This specific profiling led to the development of 2D batching, which greatly speeds up 2D rendering by reducing bottlenecks in this area.

Manuelle Timing-Funktionen

Eine weitere praktische Technik, insbesondere wenn Sie den Engpass 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 = OS.get_ticks_usec()

# Your function you want to time
update_enemies()

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

When manually timing functions, it is usually a good idea to run the function many times (1,000 or more times), instead of just once (unless it is a very slow function). The reason for doing this is that timers often have limited accuracy. Moreover, CPUs will schedule processes in a haphazard manner. Therefore, an average over a series of runs is more accurate than a single measurement.

Achten Sie beim Optimieren von Funktionen darauf, dass Sie diese entweder wiederholt überprüfen oder zeitlich festlegen. Dies gibt Ihnen ein entscheidendes Feedback, ob die Optimierung funktioniert (oder nicht).

Caches

CPU caches are something else to be particularly aware of, especially when comparing timing results of two different versions of a function. The results can be highly dependent on whether the data is in the CPU cache or not. CPUs don't load data directly from the system RAM, even though it's huge in comparison to the CPU cache (several gigabytes instead of a few megabytes). This is because system RAM is very slow to access. Instead, CPUs load data from a smaller, faster bank of memory called cache. Loading data from cache is very fast, but every time you try and load a memory address that is not stored in cache, the cache must make a trip to main memory and slowly load in some data. This delay can result in the CPU sitting around idle for a long time, and is referred to as a "cache miss".

This means that the first time you run a function, it may run slowly because the data is not in the CPU cache. The second and later times, it may run much faster because the data is in the cache. Due to this, always use averages when timing, and be aware of the effects of cache.

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-Fehlern 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 Details auf unterster Ebene für Sie. Beispielsweise stellen die Server-APIs sicher, dass Daten für das Caching bereits für Dinge wie Rendering und Physik optimiert sind. Bei der Verwendung von GDNative sollten Sie jedoch besonders auf das Caching achten.

Sprachen

Godot unterstützt eine Reihe verschiedener Sprachen und es ist zu beachten, dass es Kompromisse gibt. 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 is designed to be easy to use and iterate, and is ideal for making many types of games. However, in this language, ease of use is considered more important than performance. If you need to make heavy calculations, consider moving some of your project to one of the other languages.

C#

C# is popular and has first-class support in Godot.It offers a good compromise between speed and ease of use. Beware of possible garbage collection pauses and leaks that can occur during gameplay, though. A common approach to workaround issues with garbage collection is to use object pooling, which is outside the scope of this guide.

Andere Sprachen

Über Drittanbieter werden auch mehrere andere Sprachen unterstützt, einschließlich Rust und Javascript.

C++

Godot ist in C++ geschrieben. Die Verwendung von C++ führt normalerweise zum schnellsten Code. Auf praktischer Ebene ist es jedoch am schwierigsten, ihn auf Computern von Endbenutzern auf verschiedenen Plattformen bereitzustellen. Zu den Optionen für die Verwendung von C++ gehören GDNative und benutzerdefinierte Module.

Threads

Consider using threads when making a lot of calculations that can run in parallel to each other. Modern CPUs have multiple cores, each one capable of doing a limited amount of work. By spreading work over multiple threads, you can move further towards peak CPU efficiency.

The disadvantage of threads is that you have to be incredibly careful. As each CPU core operates independently, they can end up trying to access the same memory at the same time. One thread can be reading to a variable while another is writing: this is called a race condition. Before you use threads, make sure you understand the dangers and how to try and prevent these race conditions.

Threads can also make debugging considerably more difficult. The GDScript debugger doesn't support setting up breakpoints in threads yet.

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

Szenen-Baum

Although Nodes are an incredibly powerful and versatile concept, be aware that every node has a cost. Built-in functions such as _process() and _physics_process() propagate through the tree. This housekeeping can reduce performance when you have very large numbers of nodes (usually in the thousands).

Jeder Node wird im Godot-Renderer einzeln behandelt, sodass manchmal eine kleinere Anzahl von Nodes mit mehr in jedem einzelnen zu einer besseren Leistung führen kann.

One quirk of the SceneTree is that you can sometimes get much better performance by removing nodes from the SceneTree, rather than by pausing or hiding them. You don't have to delete a detached node. You can for example, keep a reference to a node, detach it from the scene tree using Node.remove_child(node), then reattach it later using Node.add_child(node). This can be very useful for adding and removing areas from a game, for example.

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

Physik

In einigen Situationen kann die Physik zu einem Engpass 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 häufig nicht erkennbar, kann jedoch die Leistung 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).

Another crucial aspect to physics is the physics tick rate. In some games, you can greatly reduce the tick rate, and instead of for example, updating physics 60 times per second, you may update them only 30 or even 20 times per second. This can greatly reduce the CPU load.

The downside of changing physics tick rate is you can get jerky movement or jitter when the physics update rate does not match the frames per second rendered. Also, decreasing the physics tick rate will increase input lag. It's recommended to stick to the default physics tick rate (60 Hz) in most games that feature real-time player movement.

The solution to jitter is to use fixed timestep interpolation, which involves smoothing the rendered positions and rotations over multiple frames to match the physics. You can either implement this yourself or use a third-party addon. Performance-wise, interpolation is a very cheap operation compared to running a physics tick. It's orders of magnitude faster, so this can be a significant performance win while also reducing jitter.