Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

CPU optimization

Misurare le prestazioni

Dobbiamo sapere dove sono i "colli di bottiglia" per capire come velocizzare il nostro programma. I colli di bottiglia sono le parti più lente del programma che limitano la frequenza in cui tutto può proseguire. Concentrandoci sui colli di bottiglia, possiamo concentrare i nostri sforzi sull'ottimizzare le aree che ci daranno il più grande aumento di velocità, invece di dedicare molto tempo a ottimizzare funzioni che porteranno a piccoli miglioramenti sulle prestazioni.

Per quanto riguarda la CPU, il modo più semplice per identificare i colli di bottiglia è utilizzare un profiler.

Profiler di CPU

I profiler sono eseguiti in parallelo al programma ed effettuano misurazioni di tempo per determinare la proporzione di tempo impiegata in ciascuna funzione.

L'IDE di Godot convenientemente include un profiler integrato. Non si avvia automaticamente ogni volta che si inizia il progetto: deve essere avviato e arrestato manualmente. Questo perché, come la maggior parte dei profiler, registrare queste misurazioni di tempo può rallentare considerevolmente il progetto.

Dopo la profilazione, è possibile rivedere i risultati per un determinato frame.

Screenshot del profiler di Godot

Risultati della profilazione di uno dei progetti dimostrativi.

Nota

Possiamo vedere il costo dei processi integrati, come la fisica e l'audio, nonché il costo delle nostre funzioni di scripting in basso.

Il tempo speso ad aspettare i vari server integrati potrebbe non essere conteggiato dai profiler. Questo è un bug noto.

Quando un progetto procede a rilento, spesso si nota una funzione o un processo che impiega molto più tempo rispetto agli altri. Questo è il principale collo di bottiglia e, di solito, è possibile aumentare la velocità ottimizzando quest'area.

Per ulteriori informazioni su come utilizzare il profiler integrato di Godot, consultare Pannello di debug.

Profiler esterni

Sebbene il profiler dell'IDE di Godot sia molto comodo e utile, si potrebbe volere maggiore potenza e l'abilità di profilare il codice sorgente del motore Godot stesso.

Puoi utilizzare diversi profiler C++ di terze parti a questo scopo.

Screenshot di Callgrind

Risultati di esempio da Callgrind, che fa parte di Valgrind.

Da sinistra, Callgrind elenca la percentuale di tempo trascorso dentro una funzione e le sue funzioni figlie (incluse), la percentuale di tempo trascorso all'interno della funzione stessa, escluse le funzioni figlie (solo la funzione), il numero di volte in cui la funzione viene chiamata, il nome della funzione e il file o il modulo.

In questo esempio, possiamo notare che quasi tutto il tempo viene impiegato dalla funzione Main::iteration(). Questa è la funzione principale nel codice sorgente di Godot che viene chiamata ripetutamente. Essa si occupa di disegnare i frame, simulare i tick di fisica e aggiornare i nodi e gli script. Una grande percentuale del tempo è impiegata nelle funzioni per renderizzare un canvas (66%), poiché questo esempio usa un benchmark 2D. Più in basso, vediamo che quasi il 50% del tempo è impiegato fuori dal codice di Godot, in libglapi e i965_dri (il driver grafico). Questo ci indica che una grande parte del tempo della CPU viene impiegata dal driver grafico.

Questo è un esempio eccellente perché, in un mondo ideale, solo una minima parte del tempo sarebbe impiegata nel driver grafico. Ciò indica che c'è un problema di troppa comunicazione e lavoro svolto nell'API grafica. Questa specifica analisi ha portato allo sviluppo del batching 2D, che velocizza notevolmente il rendering 2D riducendo i colli di bottiglia in quest'area.

Cronometraggio manuale delle funzioni

Un'altra tecnica pratica, soprattutto dopo aver identificato il collo di bottiglia con un profiler, è di cronometrare manualmente la funzione o l'area in testata. I dettagli variano a seconda del linguaggio, ma in GDScript, faresti quanto segue:

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))

Quando si cronometra manualmente una funzione, di solito è una buona idea eseguirla molte volte (1.000 o più volte), invece di una sola (a meno che non sia una funzione molto lenta). Il motivo è che i timer spesso hanno una precisione limitata. Inoltre, le CPU organizzano i processi a casaccio. Pertanto, una media su una serie di esecuzioni è più precisa di una singola misurazione.

As you attempt to optimize functions, be sure to either repeatedly profile or time them as you go. This will give you crucial feedback as to whether the optimization is working (or not).

Cache

Le cache della CPU sono un'altra cosa di cui essere particolarmente consapevoli, specialmente quando si confrontano i risultati dei tempi di due diverse versioni di una funzione. I risultati possono essere altamente dipendenti dal fatto che i dati siano nella cache della CPU o meno. Le CPU non caricano i dati direttamente dalla RAM di sistema, anche se è enorme in confronto alla cache della CPU (diversi gigabyte invece di pochi megabyte). Questo perché la RAM di sistema è molto lenta da raggiungere. Invece, le CPU caricano i dati da un banco di memoria più piccolo e veloce chiamato cache. Il caricamento dei dati dalla cache è molto veloce, ma ogni volta che si cerca di caricare un indirizzo di memoria che non è memorizzato nella cache, la cache deve fare un viaggio nella memoria principale e caricare lentamente alcuni dati. Questo ritardo può portare la CPU a rimanere inattiva per molto tempo, e viene chiamato "cache miss".

Questo significa che la prima volta che una funzione viene eseguita, l'esecuzione potrebbe essere lenta perché i dati non sono presenti nella cache della CPU. La seconda volta e le successive, l'esecuzione potrebbe essere molto più veloce perché i dati sono presenti nella cache. Per questo motivo, è sempre consigliabile usare le medie per cronometrare i tempi, e tenere conto dell'influenza della cache.

Understanding caching is also crucial to CPU optimization. If you have an algorithm (routine) that loads small bits of data from randomly spread out areas of main memory, this can result in a lot of cache misses, a lot of the time, the CPU will be waiting around for data instead of doing any work. Instead, if you can make your data accesses localised, or even better, access memory in a linear fashion (like a continuous list), then the cache will work optimally and the CPU will be able to work as fast as possible.

Godot di solito si occupa automaticamente di questi dettagli di basso livello. Ad esempio, le API dei server si assicurano che i dati siano già ottimizzati per la cache per cose come il rendering e la fisica. Eppure, è importante prestare particolare attenzione alla cache quando si scrivono GDExtension.

Lingue

Godot supports a number of different languages, and it is worth bearing in mind that there are trade-offs involved. Some languages are designed for ease of use at the cost of speed, and others are faster but more difficult to work with.

Le funzioni integrate del motore vengono eseguite alla stessa velocità a prescindere dal linguaggio di scripting scelto. Se il proprio progetto esegue molti calcoli nel proprio codice, si consideri di spostarli in un linguaggio più veloce.

GDScript

GDScript è progettato per essere facile da usare e iterare, ed è ideale per realizzare molti generi di giochi. Tuttavia, in questo linguaggio, la facilità d'uso è considerata più importante delle prestazioni. Se c'è bisogno di eseguire calcoli pesanti, si potrebbe voler spostare parte del proprio progetto in uno degli altri linguaggi.

C#

C# è popolare e gode di un supporto di prim'ordine in Godot. Offre un buon compromesso tra velocità e facilità d'uso. Attenzione però a possibili pause e perdite di memoria dovute alla garbage collection che possono avvenire durante il gioco. Un approccio comune per aggirare i problemi di garbage collection è di utilizzare l'object pooling, che fuori lo scopo di questa guida.

Altri linguaggi

Terze parti forniscono supporto per diversi altri linguaggi, tra cui Rust.

C++

Godot è scritto in C++. L'utilizzo di C++ generalmente risulta in codice più veloce. Tuttavia, a livello pratico, è la soluzione più complessa da distribuire sui computer degli utenti finali sulle diverse piattaforme. Le opzioni per l'utilizzo del C++ includono le GDExtension e i moduli personalizzati.

Threads

È consigliabile utilizzare i thread quando si compiono molti calcoli che possono essere in parallelo. Le CPU moderne hanno più core, ognuno dei quali è in grado di svolgere una quantità limitata di lavoro. Distribuendo il lavoro su più thread, è possibile avvicinarsi di più la massima efficienza della CPU.

Lo svantaggio dei thread è che bisogna essere estremamente cauti. Poiché ogni core della CPU opera in modo indipendente, possono finire per tentare di accedere alla stessa memoria allo stesso tempo. Un thread può leggere in una variabile mentre un altro sta scrivendo: questa è detta race condition (letteralmente "condizione di gara"). Prima di utilizzare i thread, assicurarsi di comprendere i pericoli e come cercare di prevenire queste race condition. I thread possono rendere il debug considerevolmente più difficile.

Per più informazioni sui thread, consultare Utilizzare più thread.

Albero di scene (SceneTree)

Sebbene i nodi siano un concetto incredibilmente potente e versatile, è importante sapere che ogni nodo ha un costo. Le funzioni integrate come _process() e _physics_process() si propagano attraverso l'albero. Questa gestione può ridurre le prestazioni quando si ha un numero molto elevato di nodi (il numero esatto dipende dalla piattaforma di destinazione e può variare da migliaia a decine di migliaia, quindi è importante analizzare le prestazioni su tutte le piattaforme di destinazione durante lo sviluppo).

Ogni nodo è gestito individualmente nel renderer di Godot. Pertanto, un numero minore di nodi, ma con più cose per ognuno, può portare a prestazioni migliori.

Una particolarità dello SceneTree è che a volte si possono ottenere prestazioni migliori rimuovendo i nodi dallo SceneTree, piuttosto che mettendoli in pausa o nascondendoli. Non è necessario eliminare un nodo rimosso. Ad esempio, è possibile mantenere un riferimento a un nodo, rimuoverlo dall'albero di scene tramite Node.remove_child(node), poi aggiungendolo in seguito tramite Node.add_child(node). Questo può essere molto utile per aggiungere e rimuovere aree da un gioco, ad esempio.

È possibile evitare completamente l'uso dello SceneTree tramite le API dei server. Per ulteriori informazioni, consultare la documentazione Ottimizzazione tramite i server.

Fisica

In alcune situazioni, la fisica può finire per diventare un collo di bottiglia. Occorre specialmente in mondi complessi e con un gran numero di oggetti fisici.

Ecco alcune tecniche per velocizzare la fisica:

  • Tentare di utilizzare versioni semplificate della geometria renderizzata per le forme di collisione. Spesso, questo non sarà percepibile dagli utenti finali, ma può migliorare notevolmente le prestazioni.

  • Tentare di rimuovere gli oggetti dalla fisica quando sono fuori dal campo visivo o dall'area attuale, oppure a riutilizzare gli oggetti fisici (ad esempio, permettendo 8 mostri per area e riutilizzandoli).

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. Godot has built-in physics interpolation which you can read about here. 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.