Partager via


Résoudre les problèmes d’inlining de fonction lors de la génération

Utiliser l’aperçu de fonctions Build Insights pour résoudre les problèmes l’impact de l’incorporation de fonctions sur le temps de génération dans vos projets C++.

Prérequis

  • Visual Studio 2022 17.8 ou ultérieur.
  • C++ Build Insights est activé par défaut si vous installez le développement Desktop avec la charge de travail C++ ou le développement Game avec la charge de travail C++.

Capture d’écran de Visual Studio Installer avec le développement Desktop avec la charge de travail C++ sélectionné.

La liste des composants installés est affichée. C++ Build Insights est mis en surbrillance et sélectionné, ce qui signifie qu’il est installé.

Capture d’écran de Visual Studio Installer avec le développement de jeux et la charge de travail C++ sélectionnée.

La liste des composants installés est affichée. C++ Build Insights est mis en surbrillance et sélectionné, ce qui signifie qu’il est installé.

Vue d’ensemble

Build Insights, désormais intégré à Visual Studio, vous permet d’optimiser vos temps de génération, en particulier pour les grands projets tels que les jeux AAA. Build Insights fournit des analyses telles que l’aperçu de Functions, ce qui permet d’identifier la génération de code coûteuse durant le temps de génération. Il affiche le temps nécessaire pour générer du code pour chaque fonction et affiche l’impact de __forceinline.

La directive __forceinline indique au compilateur d’effectuer l’inline d’une fonction, quelle que soit sa taille ou sa complexité. L’incorporation d’une fonction peut améliorer les performances du runtime en réduisant la surcharge liée à l’appel de la fonction. Le compromis est qu’il peut augmenter la taille du binaire et avoir un impact sur vos temps de génération.

Pour les Builds optimisées, le temps passé à générer du code contribue considérablement au temps de génération total. En général, l’optimisation des fonctions C++ se produit rapidement. Dans certains cas exceptionnels, des fonctions peuvent devenir suffisamment volumineuses et complexes pour faire pression sur l’optimiseur et ralentir vos Builds considérablement.

Dans cet article, découvrez comment utiliser l’aperçu de Functions Build Insights pour rechercher des goulots d’étranglement d’inlining dans votre Build.

Définition des options de génération

Pour mesurer les résultats de __forceinline, utilisez une build Publication car les Builds de débogage n’effectuent pas d’inline __forceinline puisque les builds de débogage utilisent le commutateur du compilateur /Ob0, ce qui désactive cette optimisation. Définissez la build pour release et x64 :

  1. Dans la liste déroulante Configurations de solution, choisissez Publication.
  2. Dans la liste déroulante Plateformes de solutions, choisissez x64.

Capture d’écran de la liste déroulante Configuration de la solution définie sur Publication et la liste déroulante Plateforme de solutions définie sur x64.

Définissez le niveau d’optimisation sur optimisations maximales :

  1. Dans l’Explorateur de solutions, cliquez avec le bouton droit sur le nom du projet et sélectionnez Propriétés.

  2. Dans les propriétés du projet, naviguez vers C/C++>Optimization.

  3. Définissez la liste déroulante Optimisation sur Optimisation maximale (Favoriser la vitesse) (/O2).

    Capture d’écran le boîte de dialogue des pages propriétés du projet. Les paramètres sont ouverts aux propriétés de configuration > , optimisation > C/C++. La liste déroulante Optimisation est définie sur Optimisation maximale (Favoriser la vitesse) (/O2).

  4. Cliquez sur OK pour fermer la boîte de dialogue.

Exécuter Build Insights

Dans un projet de votre choix et à l’aide des options de Publication définies dans la section précédente, exécutez Build Insights en choisissant dans le menu principal Générer>Exécuter Build Insights sur la sélection>Regénérer. Vous pouvez également cliquer avec le bouton droit sur un projet dans l’explorateur de solutions et choisir Exécuter Build Insights>Regénérer. Choisissez Regénérer au lieu de Générer pour mesurer le temps de génération de l’ensemble du projet, pas seulement pour les quelques fichiers qui pourraient être corrompus en ce moment.

Capture d’écran du menu principal avec Exécuter Build Insights sur la sélection > Regénérer sélectionné.

Une fois la génération est terminée, un fichier ETL (Event Trace Log) s’ouvre. Il est enregistré dans le dossier vers lequel pointe la variable d’environnement TEMP Windows. Le nom généré est basé sur l’heure de collecte.

Vue de fonction

Dans la fenêtre du fichier ETL, choisissez l’onglet Functions. Il montre les fonctions qui ont été compilées et le temps nécessaire pour générer le code pour chaque fonction. Si la quantité de code générée pour une fonction est négligeable, elle n’apparaît pas dans la liste pour éviter de détériorer les performances de la collection d’événements de build.

Capture d’écran du fichier d’aperçu de fonctions Build Insights.

Dans la colonne Nom de la fonction, performPhysicsCalculations() est mis en surbrillance et est marqué avec une icône feu.:::

La colonne Temps [sec, %] indique le temps nécessaire pour compiler chaque fonction en Temps de responsabilité de temps réel (WCTR). Cette métrique distribue le temps nécessaire au sein des fonctions, basé sur leur utilisation de threads compilateur parallèles. Par exemple, si deux threads différents compilent deux fonctions différentes simultanément dans un délai d’une seconde, le WCTR de chaque fonction est enregistré sous la forme de 0,5 seconde. Cela reflète la part proportionnelle de chaque fonction vis-à-vis du temps de compilation total, en tenant compte des ressources consommées chaque fois pendant l’exécution parallèle. Ainsi, WCTR fournit une meilleure mesure de l’impact que chaque fonction a sur le temps de génération global dans les environnements où plusieurs activités de compilation se produisent simultanément.

La colonne Taille forceinline indique approximativement le nombre d’instructions générées pour la fonction. Cliquez sur le chevron avant le nom de la fonction pour voir les fonctions inlined individuelles qui ont été développées dans cette fonction puis voir à peu près le nombre d’instructions générées pour chacun d’eux.

Vous pouvez trier la liste en cliquant sur la colonne Time pour voir quelles fonctions prennent le plus de temps pour être compiler. Une icône « fire » indique que le coût de génération de cette fonction est élevé et vaut la peine d’étudier. L’utilisation excessive des fonctions __forceinline peut ralentir considérablement la compilation.

Vous pouvez rechercher une fonction spécifique à l’aide de la zone fonctions de filtre. Si le temps de génération de code d’une fonction est trop petit, il n’apparaît pas dans l' aperçu de fonctions.

Améliorer le temps de génération en ajustant l’inlining de la fonction

Dans cet exemple, la fonction performPhysicsCalculations prend le plus de temps à compiler.

Capture d’écran de l’aperçu de fonctions Build Insights.

Dans la colonne Nom de la fonction, performPhysicsCalculations() est mis en surbrillance et est marqué avec une icône feu.

En examinant plus en détail, en sélectionnant le chevron avant cette fonction, puis en triant la colonne taille forceinline de la plus haute à la plus basse, nous voyons les plus grands contributeurs au problème.

Capture d’écran de l’aperçu de fonctions Build Insights avec une fonction étendue.

performPhysicsCalculations() est développé et affiche une longue liste de fonctions qui ont été insérées à l’intérieur. Il existe plusieurs instances de fonctions telles que complexOperation(), recursiveHelper() et sin() affichées. La colonne Forceinline Size indique que complexOperation() est la fonction inline la plus grande à 315 instructions. recursiveHelper() contient 119 instructions. Sin() contient 75 instructions mais il y a beaucoup plus d’instances de celui-ci que les autres fonctions.

Il existe des fonctions inline plus importantes, telles que Vector2D<float>::complexOperation() et Vector2D<float>::recursiveHelper() qui contribuent au problème. Mais il existe de nombreuses autres instances (pas toutes affichées ici) de Vector2d<float>::sin(float), Vector2d<float>::cos(float), Vector2D<float>::power(float,int)et Vector2D<float>::factorial(int). Lorsque vous les ajoutez, le nombre total d’instructions générées dépasse rapidement les quelques fonctions générées plus volumineuses.

En examinant ces fonctions dans le code source, nous voyons que le temps d’exécution va être passé à l’intérieur des boucles. Par exemple, voici le code pour factorial() :

static __forceinline T factorial(int n)
{
    T result = 1;
    for (int i = 1; i <= n; ++i) {
        for (int j = 0; j < i; ++j) {
            result *= (i - j) / (T)(j + 1);
        }
    }
    return result;
}

Peut-être que le coût global d’appel de cette fonction est négligeable par rapport au coût de la fonction elle-même. La création d’une fonction inline est plus bénéfique lorsque le temps nécessaire à l’appel de la fonction (envoi d’arguments sur la pile, saut vers la fonction, retour d’arguments et retour de la fonction) est approximativement similaire au temps nécessaire à l’exécution de la fonction, et lorsque la fonction est souvent sollicitée. Si ce n’est pas le cas, il peut y avoir une diminution des retours lorsque vous la rendez inline. Nous pouvons essayer de supprimer la directive __forceinline pour voir si elle aide le temps de génération. Le code de power, sin() et cos() est similaire à celui d’une boucle qui s’exécute plusieurs fois. Nous pouvons essayer de supprimer la directive __forceinline de ces fonctions également.

Nous ré-exécutons Build Insights à partir du menu principal en choisissant Générer>Exécuter Build Insights lors de la Sélection>Reconstruire. Vous pouvez également cliquer avec le bouton droit sur un projet dans l’explorateur de solutions et choisir Exécuter Build Insights>Regénérer. Nous choisissons Regénérer au lieu de Générer pour mesurer le temps de génération de l’ensemble du projet, tout comme avant, pas seulement pour les quelques fichiers qui pourraient être corrompus en ce moment.

Le temps de génération passe de 25,181 secondes à 13,376 secondes et la fonction performPhysicsCalculations ne s’affiche plus dans l’aperçu Fonctions car elle ne contribue pas suffisamment à la durée de la génération à compter.

Capture d’écran du fichier d’en-tête de vecteur 2D.

Dans la colonne Nom de la fonction, performPhysicsCalculations() est mis en surbrillance et est marqué avec une icône feu.:::

Le temps de session diagnostics correspond au temps global nécessaire à la génération, ainsi qu’à toute surcharge pour la collecte des données Build Insights.

L’étape suivante consiste à profiler l’application pour voir si les performances de l’application sont négativement affectées par la modification. Si c’est le cas, nous pouvons ajouter de manière sélective __forceinline en fonction des besoins.

Double-cliquez, cliquez avec le bouton droit ou appuyez sur Entrer lorsque vous êtes dans un fichier dans l’aperçu Fonctions pour ouvrir le code source de ce fichier.

Capture d’écran d’un clic droit sur un fichier dans l’affichage Fonctions. L’option de menu Accéder au fichier source est mise en surbrillance.

Conseils

  • Vous pouvez enregistrer le fichier ETL dans un emplacement plus permanent via Fichier>Enregistrer sous pour conserver un enregistrement de l’heure de génération. Vous pouvez ensuite la comparer aux futures builds pour voir si vos modifications améliorent le temps de génération.
  • Si vous fermez par inadvertance la fenêtre Build Insights, rouvrez-la en recherchant le fichier <dateandtime>.etl dans votre dossier temporaire. La variable d’environnement TEMP Windows fournit le chemin d’accès à votre dossier de fichiers temporaires.
  • Pour explorer les données Build Insights avec Windows Performance Analyzer (WPA), cliquez sur le bouton Ouvrir dans WPA en bas à droite de la fenêtre ETL.
  • Faites glisser des colonnes pour modifier leur ordre. Par exemple, vous préférerez peut-être placer la colonne Heure en première. Vous pouvez masquer les colonnes en cliquant avec le bouton droit sur l’en-tête de la colonne et en désélectionnant les colonnes que vous ne souhaitez pas voir.
  • La vue Fonctions fournit une zone de filtre afin de rechercher une fonction qui vous intéresse. Elle effectue des correspondances partielles sur le nom que vous fournissez.
  • Si vous oubliez comment interpréter ce que l’aperçu Fonctions essaye de vous montrer, pointez sur l’onglet pour afficher une info-bulle qui décrit l’affichage. Si vous placez le curseur sur l’onglet Fonctions, l’info-bulle indique : « Aperçu qui affiche les statistiques des fonctions où les nœuds enfants sont des fonctions force-inlined ».

Dépannage

  • Si la fenêtre Build Insights n’apparaît pas, effectuez une reconstruction au lieu d’une build. La fenêtre Build Insights n’apparaît pas si rien ne se génère réellement, ce qui peut être le cas si aucun fichier n’a changé depuis la dernière build.
  • Si l’aperçu Fonctions n’affiche aucune fonction, vous ne générez peut-être pas avec les paramètres d’optimisation appropriés. Vérifiez que vous générez la Publication avec des optimisations complètes, comme décrit dans Définir les options de génération. Aussi, si le temps de génération de code d’une fonction est trop lent, il n’apparaît pas dans la liste.

Voir aussi

Créer des conseils et astuces Insights
Fonctions Inline (C++)
Builds C++ accélérées et simplifiées : une nouvelle métrique de temps
Vidéo Générer des insights dans Visual Studio – Pure Virtual C++ 2023
Résoudre les problèmes d’impact de fichier d’en-tête au moment de la génération
Aperçu de Fonctions Build Insights dans Visual Studio 2022 17.8
Didacticiel : vcperf et Windows Performance Analyzer
Améliorer le temps de génération de code avec C++ Build Insights