Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
L’optimisation de votre code réduit le temps de calcul et les coûts. Cette étude de cas montre comment utiliser des outils de profilage Visual Studio pour identifier et résoudre les problèmes de performances dans un exemple d’application .NET. Si vous souhaitez comparer les outils de profilage, consultez Quel outil dois-je choisir ?
Ce guide couvre les points suivants :
- Comment utiliser les outils de profilage Visual Studio pour analyser et améliorer les performances.
- Stratégies pratiques pour optimiser l’utilisation du processeur, l’allocation de mémoire et les interactions de base de données.
Appliquez ces techniques pour rendre vos propres applications plus efficaces.
Étude de cas d’optimisation
L’exemple d’application .NET exécute des requêtes sur une base de données SQLite de blogs et de billets à l’aide d’Entity Framework. Il exécute de nombreuses requêtes, simulant un scénario de récupération de données réel. L’application est basée sur l’exemple de prise en main d’Entity Framework, mais utilise un jeu de données plus volumineux.
Les principaux problèmes de performances sont les suivants :
- Utilisation élevée du processeur : les calculs inefficaces ou les tâches de traitement augmentent la consommation et les coûts du processeur.
- Allocation de mémoire inefficace : une mauvaise gestion de la mémoire peut engendrer un ramassage des ordures excessif et une diminution des performances.
- Surcharges de base de données : les requêtes inefficaces et les appels de base de données excessifs dégradent les performances.
Cette étude de cas utilise des outils de profilage Visual Studio pour identifier et résoudre ces problèmes, visant à rendre l’application plus efficace et plus rentable.
Défi
La résolution de ces problèmes de performances implique plusieurs défis :
- Diagnostic des goulots d’étranglement : l’identification des causes principales d’une surcharge élevée du processeur, de la mémoire ou de la base de données nécessite une utilisation efficace des outils de profilage et une interprétation correcte des résultats.
- Contraintes de connaissances et de ressources : le profilage et l’optimisation nécessitent des compétences et une expérience spécifiques, qui peuvent ne pas toujours être disponibles.
Une approche stratégique combinant des outils de profilage, des connaissances techniques et des tests prudents est essentielle pour surmonter ces défis.
Stratégie
Voici une vue générale de l’approche dans cette étude de cas :
- Commencez avec une trace d’utilisation du processeur à l’aide de l’outil d’utilisation du processeur de Visual Studio. L’outil Utilisation du processeur de Visual Studio est un bon point de départ pour les enquêtes sur les performances.
- Collecter des traces supplémentaires pour l’analyse de la mémoire et de la base de données :
- Utilisez l’outil d’allocation d’objets .NET pour les insights sur la mémoire.
- Utilisez l’outil Base de données pour examiner les requêtes et les minutages SQL.
La collecte de données nécessite les tâches suivantes :
- Configurez l'application en version Release.
- Sélectionnez l’outil Utilisation du processeur dans Performance Profiler (Alt+F2).
- Dans le Profileur de performances, démarrez l’application et collectez une trace.
Inspecter les zones d’utilisation élevée du processeur
Après avoir collecté une trace avec l’outil Utilisation du processeur et en la chargeant dans Visual Studio, nous vérifions d’abord la page de rapport initiale .diagsession qui affiche les données résumées. Utilisez le lien Ouvrir les détails dans le rapport.
Dans la vue détaillée du rapport, ouvrez la vue Arborescence des appels. Le chemin de code qui utilise le plus de processeur dans l'application est appelé chemin chaud. L'icône de flamme du chemin chaud (
) peut aider à identifier rapidement les problèmes de performance susceptibles d'amélioration.
Dans la vue Arborescence des appels, vous pouvez constater l'utilisation élevée du processeur par la méthode GetBlogTitleX, qui représente environ 60 % de l'utilisation du processeur par l'application. Toutefois, la valeur de l’auto-processeur pour GetBlogTitleX est faible, seulement environ .10%. Contrairement à Total CPU, la valeur Self CPU exclut le temps passé dans d’autres fonctions, cela nous permet donc de regarder plus loin dans l’arborescence des appels pour le goulot d’étranglement réel.
GetBlogTitleX effectue des appels externes à deux DLL LINQ, qui utilisent la plupart du temps processeur, comme le montrent les valeurs très élevées de l'auto-processeur. Il s’agit du premier indice indiquant qu’une requête LINQ peut être une zone à optimiser.
Pour visualiser l'arborescence des appels et obtenir une vue différente des données, ouvrez le Graphique de type flamme. (Ou, cliquez avec le bouton droit sur GetBlogTitleX et choisissez Affichage dans Flame Graph.) Ici encore, il semble que la méthode GetBlogTitleX soit responsable de l’utilisation du processeur de l’application (illustrée en jaune). Les appels externes aux DLL LINQ s’affichent sous la zone GetBlogTitleX, et ils utilisent l’ensemble du temps processeur pour la méthode.
Collecter des données supplémentaires
Souvent, d’autres outils peuvent fournir des informations supplémentaires pour aider l’analyse et isoler le problème. Dans cette étude de cas, nous prenons l’approche suivante :
- Tout d’abord, examinez l’utilisation de la mémoire. Il peut y avoir une corrélation entre l’utilisation élevée du processeur et l’utilisation élevée de la mémoire. Il peut donc être utile d’examiner les deux pour isoler le problème.
- Comme nous avons identifié les DLL LINQ, nous allons également examiner l’outil Base de données.
Vérifier l’utilisation de la mémoire
Pour voir ce qui se passe avec l’application en termes d’utilisation de la mémoire, nous collectons une trace à l’aide de l’outil d’allocation d’objets .NET (pour C++, vous pouvez utiliser l’outil Utilisation de la mémoire à la place). La vue de l'arbre des appels dans la trace mémoire montre le chemin chaud et nous aide à identifier une zone où l'utilisation de la mémoire est élevée. Aucune surprise à ce stade, la méthode GetBlogTitleX semble générer beaucoup d’objets ! Plus de 900 000 allocations d’objets, en fait.
La plupart des objets créés sont des chaînes, des tableaux d’objets et Int32s. Nous pouvons voir comment ces types sont générés en examinant le code source.
Vérifier la requête dans l’outil Base de données
Dans le Profileur de performances, nous sélectionnons l’outil base de données au lieu de l’utilisation du processeur (ou, sélectionnez les deux). Lorsque nous avons collecté une trace, ouvrez l’onglet Requêtes dans la page diagnostics. Dans l’onglet Requêtes de la trace de base de données, vous pouvez voir la première ligne affichant la requête la plus longue, 2446 ms. La colonne Enregistrements indique le nombre d’enregistrements lus par la requête. Vous pouvez utiliser ces informations pour une comparaison ultérieure.
En examinant l’instruction SELECT générée par LINQ dans la colonne Requête, nous identifions la première ligne comme requête associée à la méthode GetBlogTitleX. Pour afficher la chaîne de requête complète, développez la largeur de colonne. La chaîne de requête complète est la suivante :
SELECT "b"."Url", "b"."BlogId", "p"."PostId", "p"."Author", "p"."BlogId", "p"."Content", "p"."Date", "p"."MetaData", "p"."Title"
FROM "Blogs" AS "b" LEFT JOIN "Posts" AS "p" ON "b"."BlogId" = "p"."BlogId" ORDER BY "b"."BlogId"
Notez que l’application récupère beaucoup de valeurs de colonne ici, peut-être plus que nous n’en avons besoin. Examinons le code source.
Optimiser le code
Il est temps d’examiner le code source GetBlogTitleX. Dans l’outil Base de données, cliquez avec le bouton droit sur la requête et choisissez Accéder au fichier source. Dans le code source de GetBlogTitleX, nous trouvons le code suivant qui utilise LINQ pour lire la base de données.
foreach (var blog in db.Blogs.Select(b => new { b.Url, b.Posts }).ToList())
{
foreach (var post in blog.Posts)
{
if (post.Author == "Fred Smith")
{
Console.WriteLine($"Post: {post.Title}");
}
}
}
Ce code utilise foreach boucles pour rechercher dans la base de données les blogs avec « Fred Smith » comme auteur. Vous pouvez voir qu’un grand nombre d’objets sont générés en mémoire : un nouveau tableau d’objets pour chaque blog de la base de données, des chaînes associées pour chaque URL et des valeurs pour les propriétés contenues dans les billets, comme l’ID de blog.
Nous effectuons un peu de recherche et trouvons quelques recommandations courantes pour optimiser les requêtes LINQ. Nous pouvons également gagner du temps et laisser Copilot faire la recherche pour nous.
Si nous utilisons Copilot, nous sélectionnons Demander à Copilot dans le menu contextuel et tapez la question suivante :
Can you make the LINQ query in this method faster?
Conseil
Vous pouvez utiliser des commandes obliques telles que /optimize pour vous aider à poser de bonnes questions à Copilot.
Dans cet exemple, Copilot fournit les modifications de code suggérées suivantes, ainsi qu’une explication.
public void GetBlogTitleX()
{
var posts = db.Posts
.Where(post => post.Author == "Fred Smith")
.Select(post => post.Title)
.ToList();
foreach (var postTitle in posts)
{
Console.WriteLine($"Post: {postTitle}");
}
}
Ce code inclut plusieurs modifications pour optimiser la requête :
- Ajout de la clause
Whereet élimination de l’une des bouclesforeach. - Projette uniquement la propriété Title dans l'instruction
Select, ce qui est tout ce dont nous avons besoin dans cet exemple.
Ensuite, nous retestons à l’aide des outils de profilage.
Résultats
Après avoir mis à jour le code, nous réexécutons l’outil Utilisation du processeur pour collecter une trace. La vue de l’arborescence des appels montre que GetBlogTitleX fonctionne pendant seulement 1754 ms, en utilisant 37% du total du processeur de l'application, une amélioration significative par rapport à 59%.
Basculez vers la vue Flame Graph pour afficher une autre visualisation montrant l’amélioration. Dans cette vue, GetBlogTitleX utilise également une plus petite partie du processeur.
Vérifiez les résultats dans la trace de l’outil de base de données et seuls deux enregistrements sont lus à l’aide de cette requête, au lieu de 100 000 ! En outre, la requête est beaucoup simplifiée et élimine la jointure LEFT inutile qui a été générée précédemment.
Ensuite, nous revérifions les résultats dans l’outil d’allocation d’objets .NET et nous voyons que GetBlogTitleX est uniquement responsable de 56 000 allocations d’objets, ce qui représente près d'une réduction de 95% par rapport à 900 000 !
Itérer
Plusieurs optimisations peuvent être nécessaires et nous pouvons continuer à itérer avec les modifications de code pour voir quelles modifications améliorent les performances et aident à réduire le coût de calcul.
Étapes suivantes
Les articles et billets de blog suivants fournissent plus d’informations pour vous aider à apprendre à utiliser efficacement les outils de performances de Visual Studio.
- Étude de cas : isoler un problème de performances
- Étude de cas : double performance en moins de 30 minutes
- Améliorer les performances de Visual Studio avec le nouvel outil d’instrumentation