Conseils d'optimisation du CPU et du GPU

Ce document explique comment optimiser les performances d'un jeu en utilisant des outils pour identifier et résoudre les goulots d'étranglement du processeur et du GPU.

Optimisation du processeur

Si l'analyse montre que le jeu est limité par le processeur, il est essentiel de poursuivre l'investigation. Pour ce faire, vous devez identifier les threads ou les API spécifiques qui provoquent des goulots d'étranglement et réduisent le nombre de FPS.

Pour l'optimisation du processeur, une solution universelle n'est généralement pas efficace. Au lieu de cela, vous devez identifier la charge de travail la plus exigeante en fonction du jeu ou de la scène, puis optimiser la logique et les fonctions concernées.

Outils de trace de timing du moteur de jeu

Les outils suivants peuvent vous aider à effectuer cette analyse :

Des insights incroyables

Dans les projets Unreal Engine, l'outil Unreal Insight facilite l'analyse des informations de trace temporelle pour les threads individuels qui composent un frame.

À titre d'illustration, GameThread utilise généralement la plus grande partie du temps CPU, principalement attribuable au temps de tick. De plus, une partie importante du temps de tic est consommée par les tâches associées à FActorComponentTickFunction.

Pour optimiser FActorComponentTick, il est impératif d'exclure les calculs et d'implémenter le culling pour les personnages et les objets positionnés en dehors du champ de vision de la caméra. De plus, l'utilisation d'animations basées sur le niveau de détail (LOD, Level of Detail) peut améliorer davantage les performances.

Chronologie de trace Unreal Insight montrant les temps d'exécution de GameThread, RenderThread et RHIThread
Trace Unreal Insight avec GameThread, RenderThread et RHIThread (cliquez pour agrandir).

Profileur Unity (Unity)

L'analyse à l'aide du profileur Unity révèle que le thread principal consomme plus de 45 ms, avec PostLateUpdate.FinishFrameRendering qui occupe 16,23 ms, ce qui en fait l'opération la plus gourmande en temps. Dans ce contexte, plusieurs appels d'Inl_RenderCameraStack sont observés. Il est conseillé de vérifier si les caméras activées sont nécessaires et de les optimiser en conséquence.

Chronologie du Profileur Unity montrant le thread principal en attente de Gfx.WaitForPresentOnGfxThread
Exemple de liaison GPU pour le profileur Unity (cliquez pour agrandir).

Outils de profilage au niveau du système

Utilisez les outils de profilage suivants :

Perfetto

À l'aide de la trace Perfetto, vous pouvez déterminer les affectations de cœur de processeur et les détails d'exécution de chaque thread sur un appareil Android. Cela vous permet d'identifier les goulots d'étranglement des performances en analysant les données d'exécution des threads.

Cas de surcharge du processeur

La trace indique que la charge de travail sur les threads GameThread et RenderThread provoque des retards dans la file d'attente QueuePresent du thread RHI, ce qui entraîne un scénario lié au processeur, basé sur VSync.

Trace Perfetto montrant les temps d'exécution pour GameThread, RenderThread et RHIThread
Traces Perfetto avec détails d'exécution du processeur (cliquez pour agrandir).

Cas de surcharge du GPU

La trace indique que l'exécution du GPU lui-même dépasse 25 ms, ce qui signifie qu'il s'agit d'un scénario lié au GPU.

Trace Perfetto montrant le bloc d'achèvement du GPU en attente de l'achèvement du GPU
Traces Perfetto avec des informations détaillées sur la surcharge du GPU (cliquez pour agrandir).

Simpleperf

Pour identifier les fonctions qui utilisent le plus de ressources du processeur, vous pouvez utiliser simpleperf. Pour obtenir des résultats optimaux, nous vous recommandons de trier ces fonctions afin de prioriser et de traiter en premier celles qui sont le plus utilisées.

Sortie Simpleperf affichant les fonctions avec la plus forte utilisation du processeur
Profilage du processeur Simpleperf : analyse de la hiérarchie des appels de fonction et de l'utilisation des ressources (cliquez pour agrandir).

Simpleperf vous aide à examiner les données sur les fonctions qui utilisent le plus de temps CPU. Pour optimiser l'utilisation du processeur, commencez par les fonctions qui en consomment le plus. Dans cet exemple, USkeletalMeshComponent, qui est associé à l'animation dans ActorComponentTickFunctions, utilise le plus de ressources du processeur.

Optimisation du GPU

Si l'analyse montre que le jeu est lié au GPU, une investigation plus approfondie est essentielle. Pour cela, vous devez utiliser différents outils et techniques d'optimisation et d'analyse des GPU.

Pour optimiser le GPU, utilisez un débogueur de frames afin d'analyser le pipeline de rendu et les appels de dessin pour chaque scène. De plus, vous devez bien comprendre l'architecture GPU et le comportement du pipeline pour identifier les opérations inutiles ou les zones à optimiser.

Les sections suivantes expliquent les méthodes et les outils d'optimisation des GPU.

Éliminer les RenderPasses inutiles

Pour améliorer les performances d'affichage et réduire la charge de travail du GPU, éliminez les passes de rendu inutiles. Cela inclut tout pass de rendu qui ne comporte pas d'appels de dessin ou dont la sortie n'est pas utilisée dans le frame final.

Utilisez un débogueur de GPU, tel que RenderDoc, pour analyser le pipeline de rendu et identifier les possibilités d'optimisation.

  1. Aucun appel de dessin : vérifiez si la passe de rendu inclut des appels de dessin. S'il n'y a pas d'appels de dessin, supprimez le pass.

  2. Sortie inutilisée : vérifiez si les passes suivantes accèdent aux sorties de la passe de rendu (couleur ou profondeur, par exemple) ou les affichent. Si ce n'est pas le cas, supprimez-le.

  3. Cartes fusionnables : identifiez les cartes que vous pouvez fusionner :

    • Même framebuffer ou mêmes pièces jointes
    • Opérations de chargement ou de stockage compatibles
    • Aucune barrière de dépendance entre les deux
Navigateur d'événements RenderDoc affichant les passes de rendu et les appels de dessin Vulkan
Séquence de commandes RenderPass et GPU dans RenderDoc (cliquez pour agrandir).

Minimiser les opérations de chargement ou de stockage

Les opérations de chargement ou de stockage sont gourmandes en ressources, car elles utilisent beaucoup de mémoire. Réduisez au minimum les opérations de chargement et de stockage inutiles. N'effectuez ces actions que lorsque des pièces jointes sont requises dans un RenderPass. Sinon, remplacez-les par des opérations Clear ou Don't care pour réduire la surcharge.

Optimiser les performances

Utilisez un débogueur de GPU, tel que RenderDoc, pour analyser le pipeline de rendu et identifier les opportunités d'optimisation suivantes :

  1. Chargement : si une pièce jointe de passe de rendu n'utilise pas les données d'une passe ou d'une pièce jointe précédente, une opération de chargement n'est pas nécessaire. Dans ce cas, l'utilisation de Don't care ou Clear peut réduire la surcharge.

  2. Store : si une pièce jointe de passe de rendu n'est pas utilisée après la passe de rendu actuelle, l'opération de stockage est inutile. Dans ce cas, utilisez Don't care ou Clear.

  3. Remplacer : détermine si les paramètres de chargement ou de stockage actuels peuvent être remplacés par Clear ou Don't Care sans affecter le frame final.

Navigateur d'événements et inspecteur de ressources RenderDoc analysant la mise en page et les passes de rendu d'une image
Analyse du pipeline de rendu RenderDoc (cliquez pour agrandir).

Éviter la suppression pour activer Early-Z

Early-Z améliore les performances sur les plates-formes mobiles. Toutefois, une instruction discard dans un nuanceur désactive automatiquement Early-Z. Si l'instruction discard n'est pas essentielle, supprimez-la.

Accélération Early-Z

Cette optimisation réduit considérablement les opérations du nuanceur de fragments et améliore les performances du GPU.

Early-Z Test de profondeur et de stencil

Tableau comparant les métriques de performances du CPU et du GPU lorsque Early-Z est activé ou désactivé
Impact de l'accélération Early-Z sur les performances (cliquez pour agrandir).

Optimiser les performances

Utilisez un débogueur de GPU, tel que RenderDoc, pour analyser le pipeline de rendu et identifier les opportunités d'optimisation suivantes :

  1. Utilisation de discard dans les nuanceurs de fragment : le mot clé discard empêche le GPU d'effectuer des tests de profondeur anticipés, car la visibilité du fragment n'est pas connue à l'avance.

  2. Modification de gl_FragDepth : la modification dynamique de gl_FragDepth change la profondeur d'un fragment, ce qui désactive l'optimisation Early-Z, car la profondeur finale est inconnue avant le traitement du fragment.

  3. Alpha-to-coverage activé : lorsque l'alpha-to-coverage est activé (souvent utilisé dans le rendu MSAA), la couverture des fragments dépend des valeurs alpha. Cela peut retarder les tests de profondeur et désactiver Early-Z.

Comparaison des fragments par pixel avec et sans le mot clé du nuanceur de suppression
Débogueur GPU RenderDoc pour l'analyse (cliquez pour agrandir).

Optimiser le format de texture

La sélection optimale du format de texture réduit la consommation de mémoire, améliore l'efficacité de la bande passante et améliore les performances de rendu. L'utilisation de formats de précision excessivement élevée peut gaspiller des ressources GPU sans apporter d'avantages visuels.

Optimiser les performances

Utilisez un débogueur de GPU, tel que RenderDoc, pour analyser le pipeline de rendu et identifier les opportunités d'optimisation suivantes :

  1. Utilisez D24S8 au lieu de D32S8 pour les tampons de profondeur et de stencil : l'utilisation de D24S8 pour les tampons de profondeur et de stencil réduit la consommation de mémoire de 20 % par rapport à D32S8, avec peu ou pas de différence notable dans la qualité visuelle pour la plupart des applications.
  2. Utilisez la compression ASTC pour les textures de couleur : la compression ASTC réduit considérablement l'utilisation de la mémoire de texture (jusqu'à huit fois moins par rapport aux formats non compressés) tout en préservant une qualité visuelle élevée.
  3. Utilisez des formats demi-précision flottante plutôt que des formats pleine précision flottante : utilisez R16F ou RG16F pour réduire la bande passante mémoire et la consommation de stockage. Ces formats sont bien adaptés aux tampons de post-traitement.

Optimiser la complexité de la géométrie

La réduction de la complexité géométrique améliore les performances de rendu, en particulier sur les appareils mobiles dont les capacités de GPU sont limitées. Cela implique d'utiliser un nombre réduit de sommets et de triangles, de consolider les objets pour diminuer les appels de dessin et d'éliminer la géométrie non rendue ou inutile. Des techniques telles que la simplification du maillage, le niveau de détail (LOD) et le culling de frustum ou d'occlusion peuvent réduire considérablement la charge de travail du GPU et augmenter la fréquence d'images.

Optimiser les performances

Utilisez des outils de profilage et des débogueurs de GPU, tels que RenderDoc, Android GPU Inspector ou d'autres analyseurs de performances, pour identifier les goulots d'étranglement liés à la géométrie.

  1. Réduisez le nombre de triangles : minimisez l'utilisation de polygones, en particulier pour les objets petits ou éloignés.

  2. Utilisez le niveau de détail (LOD) : des maillages plus simples sont utilisés automatiquement en fonction de la distance de la caméra.

  3. Fusionner les petits maillages : consolidez les objets statiques pour réduire les appels de dessin et la surcharge du processeur.

  4. Écrêtage du frustum et de l'occlusion : évitez d'afficher les objets qui se trouvent en dehors du champ de vision ou qui sont masqués par d'autres éléments.

Supprimer les pièces jointes inutiles

Les pièces jointes du pass de rendu (par exemple, couleur, profondeur, stencil) consomment de la bande passante mémoire et des ressources GPU, même si elles ne sont pas utilisées. La suppression des pièces jointes inutiles ou redondantes améliore les performances et réduit la consommation d'énergie, en particulier sur les plates-formes mobiles.

Optimiser les performances

Utilisez des outils de profilage et des débogueurs GPU, tels que RenderDoc, Android GPU Inspector ou d'autres analyseurs de performances, pour identifier les goulots d'étranglement liés à la géométrie.

  1. Vérifiez l'utilisation réelle : y a-t-il des appels de dessin ou des nuanceurs qui écrivent dans l'attachement ou le lisent ?
  2. Analysez la sortie du frame : utilisez RenderDoc ou des utilitaires comparables pour déterminer si la pièce jointe contribue à l'image finale.
  3. Envisagez d'utiliser des pièces jointes transitoires ou fictives : les pièces jointes transitoires ou une opération de stockage "Don't Care" doivent être utilisées pour les données temporaires qui ne nécessitent pas de stockage persistant.

Optimiser la précision des nuanceurs

L'utilisation d'une précision excessivement élevée (par exemple, highp au lieu de mediump ou lowp) dans les nuanceurs augmente la charge de travail du GPU, la consommation d'énergie et la pression des registres, en particulier sur les GPU mobiles. En utilisant la précision adéquate la plus faible pour les variables (par exemple, les positions, les couleurs, les UV), vous pouvez améliorer les performances sans impact visuel perceptible.

Table comparant les métriques de performances du CPU et du GPU lorsque vous utilisez la précision de nuanceur mediump par rapport à highp
Impact de la précision du nuanceur sur les performances (cliquez pour agrandir).

Optimiser les performances

Utilisez des outils de profilage et des débogueurs GPU tels que RenderDoc, Android GPU Inspector ou d'autres analyseurs de performances pour identifier les goulots d'étranglement liés à la géométrie.

  1. Examinez le code du nuanceur : évaluez les variables du nuanceur et vérifiez que la haute précision n'est utilisée que lorsque cela est nécessaire, par exemple pour les calculs de profondeur ou d'espace écran. Utilisez une précision moyenne ou faible pour les couleurs, les coordonnées UV ou les valeurs qui ne nécessitent pas une grande précision.

  2. Utilisez des débogueurs de GPU : les utilitaires de diagnostic, tels que RenderDoc ou les profileurs de GPU mobiles (AGI, Mali/GPU Inspector, par exemple), identifient l'utilisation élevée des registres ou les blocages de nuanceurs associés à des problèmes de précision.

Profiler "Mali Varying Usage" affichant l'interpolation 16 bits à côté du code du nuanceur utilisant mediump
Exemple d'outils de profilage et de débogueurs GPU (cliquez pour agrandir).

Activer la suppression des faces arrière

Il est souvent inutile d'afficher les triangles qui ne sont pas orientés vers la caméra (faces arrière) pour les objets solides.

Optimiser les performances

L'utilisation de VK_CULL_MODE_NONE peut avoir un impact négatif sur les performances, car elle oblige le GPU à afficher les faces avant et arrière, ce qui augmente la charge de travail de rendu.

Journal de commandes Vulkan affichant vkCmdSetCullMode défini sur VK_CULL_MODE_NONE
Journaux de débogage avec élimination des faces cachées (cliquez pour agrandir).

Réduire les superpositions dans les scènes d'UI

Éliminez les appels de dessin et les passes de rendu inutiles, en particulier dans les scènes d'UI, pour améliorer les performances de rendu et réduire la charge de travail du GPU. Par exemple, dans une scène d'UI où le monde entier est rendu avant de superposer l'UI sur l'écran, le rendu du monde devient redondant.

Optimiser les performances

Utilisez un débogueur de GPU, tel que RenderDoc, pour analyser le pipeline de rendu et identifier les opportunités d'optimisation suivantes :

  1. Vérifiez l'absence de surtirage superflu. Dans les contextes d'interface utilisateur, où l'intégralité de l'écran peut être affichée, vérifiez que les passes de rendu précédentes ne sont pas inutilement surdessinées.
  2. Activez les tests de profondeur et le tri pour optimiser les performances.
  3. Envisagez d'afficher les éléments de l'avant vers l'arrière.
Navigateur d'événements et visionneuse de textures RenderDoc identifiant un pass de rendu de recouvrement inutile
Exemple pour éliminer les appels de dessin et les passes de rendu superflus (cliquez pour agrandir).