Examiner le profilage des performances et l’efficacité du code
Dans le développement de logiciels, les performances sont une fonctionnalité. Un programme peut être fonctionnelment correct, mais s’il est trop lent ou affamé de ressources, les utilisateurs deviennent frustrés et les opportunités commerciales peuvent être perdues.
Tenez compte des observations de l’industrie suivantes :
- Pour les plateformes de commerce électronique, toutes les 100 millisecondes de latence ajoutée peuvent coûter environ 1% dans les ventes. Dans les scénarios à volume élevé, même une fraction d’une seconde peut coûter des millions de dollars en revenus perdus.
- Le ralentissement des résultats de recherche d’une demi-seconde peut réduire le trafic de 20%. Les utilisateurs s’attendent à recevoir des réponses snappy. Si votre application est lente, ils pourraient se tourner vers d'autres alternatives.
Ces observations soulignent une vérité fondamentale : les logiciels rapides offrent une meilleure expérience utilisateur et sont souvent directement corrélés avec les métriques de réussite (ventes, engagement, rétention). Pour les systèmes internes ou principaux, les performances se traduisent par une efficacité (par exemple, la gestion de plus de transactions par seconde ou la réduction des coûts cloud).
Toutefois, obtenir une performance élevée ne consiste pas en conjectures au hasard ni à optimiser sans cesse chaque ligne de code. Il s’agit de trouver les domaines spécifiques qui importent et de les améliorer, c’est là que le profilage des performances entre en jeu.
Qu’est-ce que le profilage des performances ?
Le profilage des performances est le processus d’analyse d’un programme pour comprendre comment les ressources sont utilisées , principalement le temps processeur, la mémoire, les E/S de disque/réseau, etc. Un profileur (ou technique de profilage) permet de répondre aux types de questions suivants :
- Quelles fonctions ou opérations consomment le plus de temps ?
- Où l'utilisation de la mémoire augmente-t-elle soudainement ?
- Combien de fois ce code est-il appelé ? Existe-t-il des opérations redondantes ?
- L’application attend-elle des ressources externes (comme une base de données ou un système de fichiers) ?
Le profilage est essentiellement un travail de détective sur le comportement du code lors de son exécution. Au lieu de deviner où un ralentissement peut être, vous collectez des données pour les identifier.
En tant que développeur, vous pourriez avoir des intuitions sur ce qui est lent, mais l’intuition peut être trompeuse. En d’autres termes :
- N’essayez pas d’optimiser le code qui n’est pas réellement un goulot d’étranglement (le « 97%» du code qui n’est pas critique dans le temps).
- Optimisez les « 3 % critiques » : les segments du code identifiés par le profilage comme des points chauds de performance.
L’astuce consiste à identifier les 3%critiques, et c’est exactement ce que les outils et techniques de profilage vous aident à faire.
Outils de profilage des performances
Pour profiler efficacement vos applications, vous avez besoin des outils appropriés. Il existe différents outils de profilage disponibles, allant des classes intégrées simples aux outils et solutions sophistiqués du secteur.
Chronomètre pour les mesures de minutage de base
La System.Diagnostics.Stopwatch classe dans .NET fournit un moyen simple de mesurer le temps d’exécution. Il est parfait pour les mesures rapides et ciblées :
var stopwatch = Stopwatch.StartNew();
// Code to measure
stopwatch.Stop();
Console.WriteLine($"Execution time: {stopwatch.ElapsedMilliseconds} ms");
Bien que Stopwatch soit utile pour le minutage de base, il ne fournit pas d’informations détaillées sur l’utilisation de la mémoire ou les répartitions de performances au niveau des méthodes.
BenchmarkDotNet pour un benchmark complet
BenchmarkDotNet est une bibliothèque .NET puissante conçue pour un benchmark de performances précis. Il gère automatiquement les pièges courants des évaluations, tels que la compilation juste-à-temps (JIT), les interférences lors de la gestion des déchets (ou « GC ») et la précision de mesure.
[MemoryDiagnoser]
public class MyBenchmark
{
[Benchmark]
public void Method1() => // Implementation
[Benchmark]
public void Method2() => // Implementation
}
BenchmarkDotNet fournit une analyse statistique, un suivi de l’allocation de mémoire et peut comparer plusieurs implémentations côte à côte, ce qui le rend idéal pour valider les efforts d’optimisation.
Note
Ce module de formation ne couvre pas la façon d’implémenter des outils de profilage. Toutefois, les outils de profilage sont abordés plus en détail plus loin dans ce module et l’exemple d’application utilisé dans l’exercice montre comment utiliser les outils Stopwatch et BenchmarkDotNet.
Envisager l’efficacité du code tout au long du processus de développement
Il est préférable d’incorporer des considérations sur les performances tout au long du cycle de vie du développement.
Phase de conception
Réfléchissez aux algorithmes efficaces et aux structures de données appropriées pour votre problème. Si vous prévoyez de gérer des millions d’enregistrements, une solution O(n²) peut être un problème. Les premières décisions de haut niveau (par exemple, l’utilisation d’une base de données par rapport au traitement en mémoire ou le choix entre une liste et un ensemble de hachages pour les recherches) ont des implications significatives sur les performances.
Phase d’implémentation
Écrivez votre code de manière claire et efficace pendant le développement initial. Ne vous lancez pas dans la micro-optimisation prématurée, mais soyez attentif aux inefficacités manifestes. Par exemple, si vous devez vérifier fréquemment l'appartenance, l'utilisation d'un HashSet au lieu de parcourir à plusieurs reprises une liste est à la fois plus claire en termes d'intention et plus rapide.
Phase de test de code et de profilage
Lorsque votre code ou fonctionnalité est fonctionnel, mesurez ses performances. Établir une base de référence (à quel point est-ce rapide ? Quelle quantité de mémoire utilise-t-elle ?) S’il répond à vos objectifs (par exemple, un rapport génère en moins de 2 secondes, ou si l’utilisation reste inférieure à 1 Go de mémoire), excellent. Si ce n’est pas le cas, utilisez le profilage pour examiner.
Optimisation et itération
Concentrez-vous d’abord sur les parties les plus lentes. Souvent, l’amélioration d’un ou deux goulots d’étranglement génère des avantages hors norme. Il est courant que 80% du temps d’exécution soit passé en 20% (ou moins) du code , une variante du principe Pareto dans le logiciel. Le profilage permet de trouver les 20% critiques. Après avoir apporté des modifications, testez à nouveau pour vérifier l’amélioration et vous assurer que rien n’est rompu.
Surveillance continue
En production ou à grande échelle, surveillez les métriques de performances. Utilisez des outils de supervision des applications (comme Application Insights, si vous êtes sur Azure ou d’autres) pour intercepter les performances de régression. L’utilisation réelle peut révéler différents points d’accès (par exemple, à mesure que les volumes de données augmentent ou que les modèles d’utilisation changent).
Pièges courants en matière de performances (non-GUI)
De nombreux problèmes de performances de code principal ou généraux sont connus.
Les éléments suivants fournissent une vue d’ensemble rapide :
- Inefficacités algorithmiques : Utilisation d’un algorithme ou d’une approche moins efficace que nécessaire. Exemples : tris O(n²), calculs par force brute où existe une formule, etc.
- Appels externes ou d’E/S excessifs : Lecture à partir du disque à l’intérieur d’une boucle serrée, en effectuant trop de requêtes de base de données (problème de requête N+1) ou en bloquant les appels réseau sur le thread principal.
- Modèles d’accès aux données inefficaces : Par exemple, ne pas utiliser d’index dans les requêtes de base de données ou effectuer une recherche répétée dans une liste au lieu d’utiliser une structure de recherche.
- Utilisation incorrecte de la mémoire : Création inutile d'objets volumineux ou de quantités massives d'objets de petite taille, entraînant un ramasse-miettes lourd. Conserver des références trop longtemps (ce qui cause le ballonnement de la mémoire) ou le manque de libération des ressources peut également dégrader les performances.
- Absence de concurrence ou de parallélisme : Exécution de tout ce qui se passe séquentiellement lorsque des tâches peuvent être effectuées en parallèle (sur des systèmes multicœurs ou en attente asynchrone d’E/S). Sinon, le piège opposé est la mauvaise utilisation de la concurrence, au point que la surcharge dépasse les avantages ou qu'elle introduit une contention.
-
Opérations bloquantes : Utilisation d’attentes bloquantes (
Thread.SleepE/S synchrones dans des contextes asynchrones, etc.) qui bloquent la progression.
GitHub Copilot et analyse de performances
GitHub Copilot (en particulier GitHub Copilot Agent) est un nouvel outil dans votre boîte à outils. Ce n’est pas un profileur, mais il peut agir comme un programmeur de paire IA avec une grande mémoire de problèmes de performances courants et de correctifs. Par exemple:
- GitHub Copilot peut vous expliquer le code : « Cette fonction fait-elle quelque chose d’inefficace ? » Il peut indiquer, par exemple, qu’une boucle particulière effectue un appel de base de données chaque itération, ce qui est coûteux.
- GitHub Copilot peut suggérer des améliorations : « Comment puis-je accélérer ce code ? » Il peut être recommandé d’utiliser une approche différente (par exemple, à l’aide d’une
StringBuilderconcaténation de chaîne dans une boucle, qui est une bonne pratique connue des performances en C#). - GitHub Copilot peut générer du code refactorisé si vous le sollicitez, vous faisant gagner du temps dans l'implémentation de l'optimisation.
Pensez à GitHub Copilot en tant qu’assistant qui lit d’innombrables forums de développement, pages de documentation et discussions de la communauté sur les performances. Il ne remplace pas votre compréhension, mais il peut accélérer le processus d’identification et d’application des optimisations.
Résumé
Le profilage des performances est une compétence essentielle pour les développeurs visant à créer des applications efficaces et réactives. En mesurant et en analysant systématiquement les performances du code, vous pouvez identifier les goulots d’étranglement et optimiser les parties qui comptent le plus. L’intégration des considérations relatives aux performances tout au long du cycle de vie du développement, de la conception à la surveillance continue, garantit que vos applications fonctionnent correctement, mais offrent également une expérience utilisateur supérieure.