Optimisations CPU

Mesure des performances

Pour savoir comment accélérer notre programme, nous devons savoir où se trouvent les "goulets d'étranglement". Les goulets d'étranglement sont les parties les plus lentes du programme qui limitent la vitesse à laquelle tout peut progresser. Cela nous permet de concentrer nos efforts sur l'optimisation des domaines qui nous donneront la plus grande amélioration de la vitesse, au lieu de passer beaucoup de temps à optimiser des fonctions qui conduiront à de petites améliorations des performances.

Pour le CPU, le moyen le plus simple d'identifier les goulets d'étranglement est d'utiliser un profileur.

CPU profileurs

Les profileurs fonctionnent en parallèle de votre programme et prennent des mesures de temps pour déterminer la proportion de temps passé dans chaque fonction.

L'IDE Godot dispose d'un profileur intégré. Il ne fonctionne pas à chaque fois que vous démarrez votre projet, et doit être démarré et arrêté manuellement. En effet, comme pour la plupart des profileurs, l'enregistrement de ces mesures de temps peut ralentir considérablement votre projet.

Après le profilage, vous pouvez consulter les résultats pour une image.

../../_images/godot_profiler.png

Voici les résultats d'un profil d'un des projets de démonstration.

Note

Nous pouvons voir le coût des processus intégrés tels que la physique et l'audio, ainsi que le coût de nos propres fonctions de script en bas.

Lorsqu'un projet se déroule lentement, vous verrez souvent une fonction ou un processus évident prendre beaucoup plus de temps que d'autres. C'est votre principal goulot d'étranglement, et vous pouvez généralement augmenter la vitesse en optimisant cette partie.

Pour plus d'informations sur l'utilisation du profileur dans Godot, voir Panneau de débogage.

Profileurs externes

Bien que le profileur de l'IDE Godot soit très pratique et utile, il faut parfois plus de puissance et la capacité de profiler le code source du moteur Godot lui-même.

Pour ce faire, vous pouvez utiliser un certain nombre de profileurs tiers, notamment Valgrind, VerySleepy, Visual Studio et Intel VTune.

Note

Vous devrez peut-être compiler Godot à partir des sources pour utiliser un profileur tiers afin de disposer des informations de la base de données du programme. Vous pouvez également utiliser une compilation de débogage, cependant, notez que les résultats du profilage d'une compilation de débogage seront différents de ceux d'une compilation de version, car les compilations de débogage sont moins optimisées. Les goulots d'étranglement se trouvent souvent à un endroit différent dans les versions de débogage, c'est pourquoi vous devez profiler les versions de publication chaque fois que cela est possible.

../../_images/valgrind.png

Voici des exemples de résultats de Callgrind, qui fait partie de Valgrind, sur Linux.

De gauche à droite, Callgrind indique le pourcentage de temps passé dans une fonction et ses enfants (Inclusive), le pourcentage de temps passé dans la fonction elle-même, à l'exclusion des fonctions enfants (Self), le nombre de fois que la fonction est appelée, le nom de la fonction et le fichier ou module.

Dans cet exemple, nous pouvons voir que presque tout le temps est passé sous la fonction Main::iteration(), c'est la fonction maître du code source Godot qui est appelée de façon répétée, et qui provoque le dessin de trames, la simulation des tics physiques, et la mise à jour de nœuds et de scripts. Une grande partie du temps est consacrée aux fonctions de rendu d'un canevas (66%), car cet exemple utilise un benchmark 2d. En dessous, nous voyons que presque 50% du temps est passé en dehors du code Godot dans libglapi, et i965_dri (le pilote graphique). Cela nous indique qu'une grande partie du temps CPU est passé dans le pilote graphique.

C'est en fait un excellent exemple car dans un monde idéal, seule une très petite partie du temps serait consacrée au pilote graphique, et cela indique qu'il y a un problème trop de communication et de trop travail fait dans l'API graphique. Ce profilage a conduit au développement du traitement par lot 2d, qui accélère considérablement la 2d en réduisant les goulots d'étranglement dans ce domaine.

Chronométrer manuellement des fonctions

Une autre technique pratique, surtout lorsque vous avez identifié le goulot d'étranglement à l'aide d'un profileur, consiste à chronométrer manuellement la fonction ou la zone testée. Les spécificités varient selon le langage, mais en GDScript, vous feriez ce qui suit :

var time_start = OS.get_system_time_msecs()

# Your function you want to time
update_enemies()

var time_end = OS.get_system_time_msecs()
print("Function took: " + str(time_end - time_start))

Vous pouvez envisager d'utiliser d'autres fonctions pour le temps si une autre unité de temps est plus appropriée, par exemple OS.get_system_time_secs si la fonction prendra plusieurs secondes.

Lorsque vous chronométrez manuellement des fonctions, il est généralement judicieux d'exécuter la fonction plusieurs fois (disons 1000 ou plus), au lieu d'une seule fois (à moins qu'il ne s'agisse d'une fonction très lente). Cela s'explique en grande partie par le fait que les chronomètres ont souvent une précision limitée, et que les CPU organisent les processus de manière aléatoire, de sorte qu'une moyenne sur une série d'exécutions est plus précise qu'une mesure unique.

Lorsque vous essayez d'optimiser les fonctions, veillez à les profiler ou à les chronométrer au fur et à mesure. Cela vous permettra d'obtenir un retour d'information crucial pour savoir si l'optimisation fonctionne (ou non).

Caches

Il faut également être particulièrement attentif, notamment lorsque l'on compare les résultats de chronométrage de deux versions différentes d'une fonction, au fait que les résultats peuvent être très dépendants du fait que les données se trouvent ou non dans le cache de l'unité centrale. Les CPU ne chargent pas les données directement à partir de la mémoire principale, car bien que la mémoire principale puisse être énorme (plusieurs Go), elle est très lente à accéder. Les CPU chargent plutôt des données à partir d'une banque de mémoire plus petite et plus rapide, appelée cache. Le chargement de données à partir de la mémoire cache est super rapide, mais chaque fois que vous essayez de charger une adresse mémoire qui n'est pas stockée dans la mémoire cache, la mémoire cache doit faire un voyage vers la mémoire principale et charger lentement certaines données. Ce retard peut faire que l'unité centrale reste inactive pendant un long moment, ce que l'on appelle un "cache miss".

Cela signifie que la première fois que vous exécutez une fonction, elle peut être lente, car les données ne sont pas en mémoire cache. La deuxième fois et les suivantes, elle peut s'exécuter beaucoup plus rapidement parce que les données sont en mémoire cache. Il faut donc toujours utiliser des moyennes lors du chronométrage, et être conscient des effets de cache.

La compréhension de la mise en cache est également cruciale pour l'optimisation CPU. Si vous disposez d'un algorithme (routine) qui charge de petits morceaux de données à partir de zones de la mémoire principale réparties de manière aléatoire, cela peut entraîner de nombreux cache misses, la plupart du temps, le CPU attendra des données au lieu d'effectuer un travail quelconque. Au lieu de cela, si vous pouvez faire en sorte que vos accès aux données soient localisés, ou mieux encore, si vous accédez à la mémoire de manière linéaire (comme une liste continue), alors le cache fonctionnera de manière optimale et le CPU pourra travailler aussi vite que possible.

Godot s'occupe généralement de ces détails de bas niveau pour vous. Par exemple, les API du serveur s'assurent que les données sont déjà optimisées pour la mise en cache pour des choses comme le rendu et la physique. Mais vous devez être particulièrement attentif à la mise en cache lorsque vous utilisez GDNative.

Langages

Godot prend en charge un certain nombre de langues différentes, et il convient de garder à l'esprit qu'il y a des compromis à faire : certains langages sont conçues pour être faciles à utiliser, au prix de la rapidité, et d'autres sont plus rapides mais plus difficiles à utiliser.

Les fonctions intégrées du moteur fonctionnent à la même vitesse, quel que soit le langage de script que vous choisissez. Si votre projet effectue beaucoup de calculs dans son propre code, envisagez de déplacer ces calculs vers un langage plus rapide.

GDScript

Le GDScript est conçu pour être facile à utiliser et à itérer, et est idéal pour réaliser de nombreux types de jeux. Toutefois, la facilité d'utilisation est considérée comme plus importante que la performance, donc si vous devez faire des calculs lourds, pensez à déplacer une partie de votre projet vers l'un des autres langages.

C#

Le C# est populaire et bénéficie d'un soutien de premier ordre dans Godot. Il offre un bon compromis entre vitesse et facilité d'utilisation.

Autres langages

Des tiers fournissent un support pour plusieurs autres langages, notamment Rust et Javascript.

C++

Godot est écrit en C++. L'utilisation du C++ permet généralement d'obtenir le code le plus rapide, mais d'un point de vue pratique, il est le plus difficile à déployer sur les machines des utilisateurs finaux sur différentes plateformes. Les options d'utilisation du C++ comprennent GDNative et les modules personnalisés.

Sujets

Pensez à utiliser des threads lorsque vous effectuez de nombreux calculs qui peuvent être parallèles les uns aux autres. Les CPU modernes ont plusieurs cœurs, chacun capable d'effectuer une quantité de travail limitée. En répartissant le travail sur plusieurs threads, vous pouvez aller plus loin vers une efficacité maximale du CPU.

L'inconvénient des threads est qu'il faut être incroyablement prudent. Comme chaque cœur de CPU fonctionne indépendamment, ils peuvent finir par essayer d'accéder à la même mémoire en même temps. Un thread peut lire une variable alors qu'un autre est en train d'écrire. Avant d'utiliser les threads, assurez-vous de bien comprendre les dangers et comment essayer de prévenir ces conditions de concurrence.

Pour plus d'informations sur les threads, voir Utilisation de plusieurs threads.

L'arbre de scène

Bien que les nœuds soient un concept incroyablement puissant et polyvalent, sachez que chaque nœud a un coût. Des fonctions intégrées telles que _processus() et _processus_physique() se propagent dans l'arbre. Cette gestion interne peut réduire les performances lorsque vous avez un très grand nombre de nœuds.

Chaque nœud est traité individuellement dans le moteur de rendu Godot, de sorte que parfois un nombre plus petit de nœuds avec plus dans chacun peut conduire à une meilleure performance.

L'une des bizarreries de SceneTree est que vous pouvez parfois obtenir de bien meilleures performances en enlevant des nœuds de l'arbre de scène, plutôt qu'en les mettant en pause ou en les cachant. Vous n'avez pas besoin de supprimer un nœud détaché. Vous pouvez par exemple conserver une référence à un nœud, le détacher de l'arbre des scènes, puis le rattacher plus tard. Cela peut être très utile pour ajouter et supprimer des zones d'un jeu par exemple.

Vous pouvez éviter complètement SceneTree en utilisant les API serveur. Pour plus d'informations, voir Optimisation à l'aide de serveurs.

Physique

Dans certaines situations, la physique peut finir par devenir un goulot d'étranglement, en particulier avec des mondes complexes et un grand nombre d'objets physiques.

Quelques techniques pour accélérer la physique :

  • Essayez d'utiliser des versions simplifiées de votre géométrie rendue pour la physique. Souvent, les utilisateurs finaux ne s'en apercevront pas, mais cela peut améliorer considérablement les performances.
  • Essayez de retirer des objets de la physique lorsqu'ils sont hors de vue / en dehors de la zone actuelle, ou de réutiliser des objets de la physique (peut-être que vous autorisez 8 monstres par zone, par exemple, et que vous les réutilisez).

Un autre aspect crucial de la physique est le taux de taux de rafraîchissement de la physique. Dans certains jeux, vous pouvez réduire considérablement le taux de taux de rafraîchissement, et au lieu, par exemple, de mettre à jour la physique 60 fois par seconde, vous pouvez la mettre à jour à 20, voire 10 fois par seconde. Cela peut réduire considérablement la charge du CPU.

L'inconvénient de la modification du taux de rafraîchissement de la physique est que vous pouvez obtenir un mouvement saccadé ou du jitter lorsque le taux de rafraîchissement de la physique ne correspond pas à celui du rendu des images.

La solution à ce problème est la 'fixed timestep interpolation', qui consiste à lisser les positions et les rotations rendues sur plusieurs trames pour qu'elles correspondent à la physique. Vous pouvez soit l'implémenter vous-même, soit utiliser un addon tiers. L'interpolation est une opération très peu coûteuse, en termes de performances, par rapport au rafraîchissement de la physique, des ordres de grandeur plus rapides, donc cela peut être un gain important, ainsi que la réduction du jitter.