Introduction : pourquoi la performance est-elle importante dès le début ?
\nChaque développeur C# rencontre tôt ou tard une situation où l'application commence à ralentir. Souvent, les problèmes de performance sont posés dès les premières étapes — à cause d'algorithmes non optimaux, d'une mauvaise gestion de la mémoire ou d'une utilisation excessive de LINQ sans comprendre son fonctionnement interne. Pour les débutants, ce sujet peut sembler complexe, mais en réalité, les principes de base de l'optimisation sont assez simples et peuvent être maîtrisés en quelques heures de pratique.
\nDans cet article, nous aborderons quatre aspects clés qui vous aideront à écrire des applications C# rapides et efficaces sur la plateforme .NET. Vous apprendrez à profiler le code, à optimiser les requêtes LINQ, à gérer correctement la mémoire et à utiliser l'asynchrone. Les exemples de code seront aussi proches que possible des tâches réelles rencontrées par les développeurs.
\n\n1. Profilage : la première étape vers l'optimisation
\nAvant d'optimiser quoi que ce soit, il faut comprendre ce qui ralentit. Les suppositions intuitives sont souvent erronées. Le profilage est le processus de mesure des performances de votre code à l'aide d'outils spécialisés.
\n\nOutils de profilage
\n- \n
- Visual Studio Diagnostic Tools — un outil intégré, disponible dans les éditions Community et supérieures. Permet de suivre l'utilisation du CPU, de la mémoire et le temps d'exécution. \n
- JetBrains dotMemory / dotTrace — des utilitaires commerciaux puissants pour l'analyse de la mémoire et du temps d'exécution. \n
- BenchmarkDotNet — une bibliothèque légère pour le micro-benchmarking. Idéale pour comparer deux implémentations d'une même méthode. \n
Exemple : mesure du temps d'exécution
\nUtilisons Stopwatch pour une mesure simple :
using System.Diagnostics;\n\nvar stopwatch = Stopwatch.StartNew();\n// Votre code\nfor (int i = 0; i < 1000000; i++)\n{\n // Une opération quelconque\n}\nstopwatch.Stop();\nConsole.WriteLine($"Temps d'exécution : {stopwatch.ElapsedMilliseconds} ms");\nConseil : pour des mesures précises, utilisez BenchmarkDotNet, car Stopwatch ne tient pas compte des frais généraux de la compilation JIT.
2. Gestion de la mémoire et des chaînes de caractères
\nLa gestion de la mémoire dans .NET est automatisée (Garbage Collector), mais cela ne signifie pas qu'on peut l'ignorer. L'un des problèmes les plus fréquents est la création excessive de chaînes de caractères.
\n\nProblème : concaténation de chaînes dans une boucle
\nLes chaînes en C# sont immuables. Chaque opération + crée une nouvelle chaîne, ce qui entraîne des allocations constantes et une pression sur le GC :
string result = "";\nfor (int i = 0; i < 10000; i++)\n{\n result += i.ToString(); // MAUVAIS : crée 10000 nouvelles chaînes\n}\n\nSolution : StringBuilder
\nLa classe StringBuilder travaille avec un tampon de caractères et minimise les allocations :
var sb = new StringBuilder();\nfor (int i = 0; i < 10000; i++)\n{\n sb.Append(i);\n}\nstring result = sb.ToString(); // BON : une seule allocation à la fin\n\nÉvitez le boxing
\nLe boxing (encapsulation des types valeur dans un objet) est une autre source d'allocations cachées. Exemple :
\nArrayList list = new ArrayList();\nlist.Add(42); // Boxing : int est encapsulé dans object\n\n// Mieux vaut utiliser des collections génériques :\nList<int> list2 = new List<int>();\nlist2.Add(42); // Sans boxing\n\n3. Optimisation des requêtes LINQ
\nLINQ est un outil puissant, mais une mauvaise utilisation peut fortement ralentir l'application. Le problème principal est les parcours multiples des collections.
\n\nProblème : itérations multiples
\nChaque appel à Where(), Select(), Count() peut parcourir à nouveau la collection si le résultat n'est pas matérialisé :
var numbers = Enumerable.Range(1, 1000000);\nvar evenNumbers = numbers.Where(n => n % 2 == 0);\nint coun