Optimisation CPU

Mesure des performances

Nous devons savoir où se trouvent les "goulets d'étranglement" pour savoir comment accélérer notre programme. Les goulets d'étranglement sont les parties les plus lentes du programme qui limitent la vitesse à laquelle tout peut progresser. Se concentrer sur les goulets d'étranglement, 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 : il 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
Screenshot du profileur de Godot

Résultats d'un profilage 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.

Le temps passé à attendre les différents serveurs intégrés peut ne pas être comptabilisé dans les profileurs. Il s'agit d'un bug connu.

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 intégré de 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, HotSpot, Visual Studio et Intel VTune.

Note

Vous devrez peut-être compiler Godot à partir des sources pour utiliser un profileur tiers. Ceci est nécessaire pour obtenir des symboles de débogage. 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 publication, 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.

Capture d'écran de Callgrind

Exemples de résultats de Callgrind, qui fait partie de Valgrind.

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 dans le code source de Godot qui est appelée de façon répétée. Provoquant le dessin des images, 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 de 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. Cela indique qu'il y a un problème avec 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 le rendu 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_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)

Lorsque vous chronométrez manuellement des fonctions, il est généralement judicieux d'exécuter la fonction plusieurs fois (1000 fois 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 timers ont souvent une précision limitée, et que les CPU organisent les processus de manière aléatoire. Par conséquent, 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

Les caches duCPU sont une autre chose à laquelle il faut être particulièrement attentif, surtout lorsqu'on compare les résultats de chronométrage de deux versions différentes d'une fonction. Les résultats peuvent être très dépendants du fait que les données se trouvent ou non dans le cache du CPU. Les CPU ne chargent pas les données directement à partir de la RAM du système, même si celle-ci est énorme par rapport au cache du CPU (plusieurs gigaoctets au lieu de quelques mégaoctets). Cela s'explique par le fait que la mémoire vive du système est très lente en accès. Au lieu de cela, les CPU chargent 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 très 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 le CPU reste inactif 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

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. Si vous devez faire des calculs lourds, pensez à déplacer une partie de votre projet vers l'un des autres langages.

C#

C# est populaire et bénéficie d'un support de premier ordre dans Godot. Il offre un bon compromis entre vitesse et facilité d'utilisation. Attention toutefois aux éventuelles pauses de garbage collection et aux fuites qui peuvent se produire pendant le gameplay. Une approche courante pour contourner les problèmes liés au gameplay consiste à utiliser l'object pooling, ce qui n'entre pas dans le cadre de ce guide.

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. Cependant, 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 pour utilisater le C++ comprennent GDNative et les modules personnalisés.

Tâches Parallèles

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 : c'est ce qu'on appelle race condition. Avant d'utiliser les threads, assurez-vous de bien comprendre les dangers et comment essayer de prévenir ces race conditions.

Les threads peuvent également rendre le débogage beaucoup plus difficile. Le débogueur GDScript ne prend pas encore en charge la mise en place de breakpoints dans les threads.

Pour plus d'informations sur les threads, voir Utiliser plusieurs fils d'exécution.

ArbreDesScènes

Bien que les Nodes 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 (généralement des milliers).

Chaque nœud est traité individuellement dans le moteur de rendu Godot. Par conséquent, 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 du SceneTree, 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 avec Node.remove_child(node), puis le rattacher plus tard avec Node.add_child(node). 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. C'est particulièrement le cas 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 à 30, voire 20 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. De plus, la diminution du taux de rafraîchissement des données physiques augmente le délai d'entrée. Il est recommandé de s'en tenir à la fréquence de rafraîchissement par défaut (60 Hz) dans la plupart des jeux qui présentent des mouvements du joueur en temps réel.

La solution au jitter est d'utiliser fixed timestep interpolation, qui consiste à lisser les positions et les rotations rendues sur plusieurs images pour correspondre à la physique. Vous pouvez soit l'implémenter vous-même, soit utiliser un addon tiers. Du point de vue des performances, l'interpolation est une opération très peu coûteuse par rapport à l'exécution d'une tique de physique. Elle est plus rapide d'un ordre de grandeur, ce qui peut être un gain de performance significatif tout en réduisant le jitter.