Performances dans .NET Core

Performances dans .NET Core

Performances dans .NET Core

Salut tout le monde! Cet article est un recueil de bonnes pratiques que mes collègues et moi utilisons depuis longtemps lorsque nous travaillons sur différents projets.

Informations sur la machine sur laquelle les calculs ont été effectués :BenchmarkDotNet=v0.11.5, système d'exploitation=Windows 10.0.18362
Processeur Intel Core i5-8250U 1.60 GHz (Kaby Lake R), 1 processeur, 8 cœurs logiques et 4 cœurs physiques
Kit de développement logiciel .NET Core = 3.0.100
[Hôte] : .NET Core 2.2.7 (CoreCLR 4.6.28008.02, CoreFX 4.6.28008.03), RyuJIT 64 bits
Noyau : .NET Core 2.2.7 (CoreCLR 4.6.28008.02, CoreFX 4.6.28008.03), RyuJIT 64 bits
[Hôte] : .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), RyuJIT 64 bits
Noyau : .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), RyuJIT 64 bits

Travail=Core Runtime=Core

ToList vs ToArray et cycles


J'avais prévu de préparer ces informations avec la sortie de .NET Core 3.0, mais ils m'ont devancé, je ne veux pas voler la gloire de quelqu'un d'autre et copier les informations d'autres personnes, je vais donc simplement le souligner lien vers un bon article où la comparaison est décrite en détail.

En mon nom personnel, je souhaite juste vous présenter mes mesures et résultats ; j'y ai ajouté des boucles inversées pour les amateurs du « style C++ » d'écriture de boucles.

Code:

public class Bench
    {
        private List<int> _list;
        private int[] _array;

        [Params(100000, 10000000)] public int N;

        [GlobalSetup]
        public void Setup()
        {
            const int MIN = 1;
            const int MAX = 10;
            Random random = new Random();
            _list = Enumerable.Repeat(0, N).Select(i => random.Next(MIN, MAX)).ToList();
            _array = _list.ToArray();
        }

        [Benchmark]
        public int ForList()
        {
            int total = 0;
            for (int i = 0; i < _list.Count; i++)
            {
                total += _list[i];
            }

            return total;
        }
        
        [Benchmark]
        public int ForListFromEnd()
        {
            int total = 0;t
            for (int i = _list.Count-1; i > 0; i--)
            {
                total += _list[i];
            }

            return total;
        }

        [Benchmark]
        public int ForeachList()
        {
            int total = 0;
            foreach (int i in _list)
            {
                total += i;
            }

            return total;
        }

        [Benchmark]
        public int ForeachArray()
        {
            int total = 0;
            foreach (int i in _array)
            {
                total += i;
            }

            return total;
        }

        [Benchmark]
        public int ForArray()
        {
            int total = 0;
            for (int i = 0; i < _array.Length; i++)
            {
                total += _array[i];
            }

            return total;
        }
        
        [Benchmark]
        public int ForArrayFromEnd()
        {
            int total = 0;
            for (int i = _array.Length-1; i > 0; i--)
            {
                total += _array[i];
            }

            return total;
        }
    }

Les vitesses de performances dans .NET Core 2.2 et 3.0 sont presque identiques. Voici ce que j'ai pu obtenir dans .NET Core 3.0 :

Performances dans .NET Core

Performances dans .NET Core

Nous pouvons conclure que le traitement itératif d'une collection Array est plus rapide en raison de ses optimisations internes et de son allocation explicite de la taille de la collection. Il convient également de rappeler qu'une collection List a ses propres avantages et que vous devez utiliser la bonne collection en fonction des calculs requis. Même si vous écrivez une logique pour travailler avec des boucles, n'oubliez pas qu'il s'agit d'une boucle ordinaire et qu'elle est également soumise à une éventuelle optimisation de boucle. Un article a été publié sur Habr il y a assez longtemps : https://habr.com/ru/post/124910/. Sa lecture est toujours d'actualité et recommandée.

Jeter

Il y a un an, j'ai travaillé dans une entreprise sur un projet existant, dans ce projet, il était normal de traiter la validation sur le terrain via une construction try-catch-throw. J'ai déjà compris alors qu'il s'agissait d'une logique commerciale malsaine pour le projet, alors autant que possible, j'ai essayé de ne pas utiliser une telle conception. Mais voyons pourquoi l’approche de gestion des erreurs avec une telle construction est mauvaise. J'ai écrit un petit code pour comparer les deux approches et fait des benchmarks pour chaque option.

Code:

        public bool ContainsHash()
        {
            bool result = false;
            foreach (var file in _files)
            {
                var extension = Path.GetExtension(file);
                if (_hash.Contains(extension))
                    result = true;
            }

            return result;
        }

        public bool ContainsHashTryCatch()
        {
            bool result = false;
            try
            {
                foreach (var file in _files)
                {
                    var extension = Path.GetExtension(file);
                    if (_hash.Contains(extension))
                        result = true;
                }
                
                if(!result) 
                    throw new Exception("false");
            }
            catch (Exception e)
            {
                result = false;
            }

            return result;
        }

Les résultats dans .NET Core 3.0 et Core 2.2 ont un résultat similaire (.NET Core 3.0) :

Performances dans .NET Core

Performances dans .NET Core

Try catch rend le code plus difficile à comprendre et augmente le temps d'exécution de votre programme. Mais si vous avez besoin de cette construction, vous ne devez pas insérer les lignes de code qui ne sont pas censées gérer les erreurs - cela rendra le code plus facile à comprendre. En fait, ce n'est pas tant la gestion des exceptions qui charge le système, mais plutôt le lancement des erreurs elles-mêmes via la construction throw new Exception.

La génération d'exceptions est plus lente que certaines classes qui collecteront l'erreur dans le format requis. Si vous traitez un formulaire ou des données et que vous savez clairement quelle devrait être l'erreur, pourquoi ne pas la traiter ?

Vous ne devez pas écrire une construction throw new Exception() si cette situation n'est pas exceptionnelle. Gérer et lancer une exception coûte très cher !!!

ToLower, ToLowerInvariant, ToUpper, ToUpperInvariant

Au cours de mes 5 années d'expérience sur la plateforme .NET, j'ai rencontré de nombreux projets utilisant la correspondance de chaînes. J'ai également vu l'image suivante : il existait une solution Enterprise avec de nombreux projets, chacun effectuant des comparaisons de chaînes différemment. Mais que faut-il utiliser et comment l’unifier ? Dans le livre CLR via C# de Richter, j'ai lu des informations selon lesquelles la méthode ToUpperInvariant() est plus rapide que ToLowerInvariant().

Extrait du livre :

Performances dans .NET Core

Bien sûr, je n'y ai pas cru et j'ai décidé de faire quelques tests sur le .NET Framework et le résultat m'a choqué - une augmentation des performances de plus de 15 %. Puis, en arrivant au travail le lendemain matin, j'ai montré ces mesures à mes supérieurs et leur ai donné accès au code source. Après cela, 2 projets sur 14 ont été modifiés pour s'adapter aux nouvelles mesures, et étant donné que ces deux projets existaient pour traiter d'énormes tableaux Excel, le résultat était plus que significatif pour le produit.

Je vous présente également des mesures pour différentes versions de .NET Core, afin que chacun d'entre vous puisse faire un choix vers la solution la plus optimale. Et je veux juste ajouter que dans l'entreprise où je travaille, nous utilisons ToUpper() pour comparer les chaînes.

Code:

public const string defaultString =  "VXTDuob5YhummuDq1PPXOHE4PbrRjYfBjcHdFs8UcKSAHOCGievbUItWhU3ovCmRALgdZUG1CB0sQ4iMj8Z1ZfkML2owvfkOKxBCoFUAN4VLd4I8ietmlsS5PtdQEn6zEgy1uCVZXiXuubd0xM5ONVZBqDu6nOVq1GQloEjeRN8jXrj0MVUexB9aIECs7caKGddpuut3";

        [Benchmark]
        public bool ToLower()
        {
            return defaultString.ToLower() == defaultString.ToLower();
        }

        [Benchmark]
        public bool ToLowerInvariant()
        {
            return defaultString.ToLowerInvariant() == defaultString.ToLowerInvariant();
        }

        [Benchmark]
        public bool ToUpper()
        {
            return defaultString.ToUpper() == defaultString.ToUpper();
        }

        [Benchmark]
        public bool ToUpperInvariant()
        {
            return defaultString.ToUpperInvariant() == defaultString.ToUpperInvariant();
        }

Performances dans .NET Core

Performances dans .NET Core

Dans .NET Core 3.0, l’augmentation pour chacune de ces méthodes est d’environ x2 et équilibre les implémentations entre elles.

Performances dans .NET Core

Performances dans .NET Core

Compilation de niveaux

Dans mon dernier article j'ai décrit brièvement cette fonctionnalité, je souhaite corriger et compléter mes propos. La compilation multiniveau accélère le temps de démarrage de votre solution, mais vous sacrifiez le fait que certaines parties de votre code seront compilées dans une version plus optimisée en arrière-plan, ce qui peut introduire une petite surcharge. Avec l'avènement de NET Core 3.0, le temps de construction des projets pour lesquels la compilation de niveaux est activée a diminué et les bogues associés à cette technologie ont été corrigés. Auparavant, cette technologie entraînait des erreurs dans les premières requêtes dans ASP.NET Core et se figeait lors de la première build en mode compilation multi-niveaux. Il est actuellement activé par défaut dans .NET Core 3.0, mais vous pouvez le désactiver si vous le souhaitez. Si vous occupez le poste de chef d'équipe, senior, intermédiaire ou si vous êtes chef de service, alors vous devez comprendre que le développement rapide d'un projet augmente la valeur de l'équipe et cette technologie vous permettra de gagner du temps pour les deux développeurs. et le temps du projet lui-même.

Niveau .NET supérieur

Mettez à niveau votre version .NET Framework / .NET Core. Souvent, chaque nouvelle version apporte des gains de performances supplémentaires et ajoute de nouvelles fonctionnalités.

Mais quels sont exactement les avantages ? Examinons-en quelques-uns :

  • .NET Core 3.0 a introduit les images R2R qui réduiront le temps de démarrage des applications .NET Core.
  • Avec la version 2.2, la Tier Compilation est apparue, grâce à laquelle les programmeurs passeront moins de temps à lancer un projet.
  • Prise en charge des nouvelles normes .NET.
  • Prise en charge d'une nouvelle version du langage de programmation.
  • Optimisation, à chaque nouvelle version l'optimisation des bibliothèques de base Collection/Struct/Stream/String/Regex et bien plus encore s'améliore. Si vous migrez du .NET Framework vers .NET Core, vous obtiendrez une amélioration considérable des performances dès le départ. À titre d'exemple, je joins un lien vers certaines des optimisations ajoutées à .NET Core 3.0 : https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-core-3-0/

Performances dans .NET Core

Conclusion

Lors de l'écriture de code, il convient de prêter attention aux différents aspects de votre projet et d'utiliser les fonctionnalités de votre langage de programmation et de votre plate-forme pour obtenir le meilleur résultat. Je serais heureux si vous partagez vos connaissances liées à l'optimisation dans .NET.

Lien vers github

Source: habr.com

Ajouter un commentaire