Leistung in .NET Core

Leistung in .NET Core

Leistung in .NET Core

Hallo zusammen! Dieser Artikel ist eine Sammlung von Best Practices, die meine Kollegen und ich über lange Zeiträume in unterschiedlichen Projekten angewendet haben.

Informationen zu dem Computer, auf dem die Berechnungen durchgeführt wurden:BenchmarkDotNet=v0.11.5, OS=Windows 10.0.18362
Intel Core i5-8250U CPU 1,60 GHz (Kaby Lake R), 1 CPU, 8 logische und 4 physische Kerne
.NET Core SDK=3.0.100
[Host]: .NET Core 2.2.7 (CoreCLR 4.6.28008.02, CoreFX 4.6.28008.03), 64bit RyuJIT
Core: .NET Core 2.2.7 (CoreCLR 4.6.28008.02, CoreFX 4.6.28008.03), 64bit RyuJIT
[Host]: .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), 64bit RyuJIT
Core: .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), 64bit RyuJIT

Job=Core Runtime=Core

ToList vs ToArray und Schleifen


Ich hatte vor, diese Informationen mit dem Erscheinen von .NET Core 3.0 vorzubereiten, aber ich wurde überholt. Ich möchte nicht die Lorbeeren anderer stehlen oder deren Informationen kopieren, daher werde ich einfach auf einen guten Artikel verweisen, in dem der Vergleich ausführlich beschrieben ist..

Ich möchte euch zudem meine Messungen und Ergebnisse präsentieren. Ich habe Rückwärtszyklen für Liebhaber des 'C++-Stils' hinzugefügt.

Code:

public class Bench
    {
        private List _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  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  0; i--)
            {
                total += _array[i];
            }

            return total;
        }
    }

Die Geschwindigkeiten in .NET Core 2.2 und 3.0 sind nahezu identisch. Hier ist, was ich in .NET Core 3.0 erreichen konnte:

Leistung in .NET Core

Leistung in .NET Core

Wir können feststellen, dass die zyklische Verarbeitung von Array-Sammlungen aufgrund ihrer internen Optimierungen und der expliziten Festlegung der Sammlungsgröße schneller ist. Es ist auch wichtig zu beachten, dass die List-Kollektion ihre eigenen Vorteile hat, und Sie sollten die geeignete Kollektion je nach den erforderlichen Berechnungen auswählen. Selbst wenn Sie Logik für Schleifen schreiben, sollten Sie nicht vergessen, dass es sich um eine gewöhnliche Schleife handelt, die ebenfalls für mögliche Optimierungen anfällig ist. Auf Habr erschien vor einiger Zeit ein Artikel: https://habr.com/ru/post/124910/. Dieser ist nach wie vor relevant und wird zur Lektüre empfohlen.

Throw

Vor einem Jahr arbeitete ich an einem Legacy-Projekt in einer Firma, wo es als normal galt, die Validierung von Feldern mithilfe einer try-catch-throw-Konstruktion zu behandeln. Ich erkannte bereits damals, dass diese Art der Geschäftslogik ungesund für das Projekt war, weshalb ich nach Möglichkeit versuchte, auf diese Konstruktion zu verzichten. Lassen Sie uns jedoch erörtern, warum dieser Ansatz der Fehlerbehandlung problematisch ist. Ich habe ein kleines Stück Code geschrieben, um zwei Ansätze zu vergleichen und Benchmarks für jede Variante zu erstellen.

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;
        }

Die Ergebnisse in .NET Core 3.0 und Core 2.2 zeigen ein ähnliches Ergebnis (.NET Core 3.0):

Leistung in .NET Core

Leistung in .NET Core

Der Einsatz von Try-Catch erschwert das Verständnis des Codes und erhöht die Ausführungszeit Ihres Programms. Wenn Sie jedoch diese Konstruktion benötigen, sollten Sie keine Codezeilen einfügen, für die keine Fehlerbehandlung erwartet wird – das erleichtert das Verständnis des Codes. Tatsächlich belastet nicht die Verarbeitung von Ausnahmen das System, sondern das Auslösen der Fehler durch die Konstruktion throw new Exception.

Das Auslösen von Ausnahmen funktioniert langsamer als eine Klasse, die den Fehler im gewünschten Format erfasst. Wenn Sie ein Formular oder Daten verarbeiten und genau wissen, welcher Fehler auftreten sollte, warum sollten Sie diesen nicht behandeln?

Es ist nicht ratsam, die Konstruktion throw new Exception() zu verwenden, wenn diese Situation nicht außergewöhnlich ist. Die Verarbeitung und das Auslösen von Ausnahmen sind sehr kostspielig!!!

ToLower, ToLowerInvariant, ToUpper, ToUpperInvariant

In meinen fünf Jahren Erfahrung mit der .NET-Plattform bin ich auf viele Projekte gestoßen, die String-Vergleiche verwendet haben. Ich habe auch folgendes Bild gesehen: Es gab eine Unternehmenslösung mit zahlreichen Projekten, die alle String-Vergleiche unterschiedlich durchführten. Was sollten wir verwenden und wie können wir das vereinheitlichen? In dem Buch 'CLR via C#' von Richter habe ich die Information gelesen, dass die Methode ToUpperInvariant() schneller ist als ToLowerInvariant().

Auszug aus dem Buch:

Leistung in .NET Core

Natürlich konnte ich das nicht glauben und beschloss, einige Tests durchzuführen, damals noch auf dem .NET Framework. Das Ergebnis hat mich schockiert — über 15 % Leistungssteigerung. Am nächsten Morgen zeigte ich meiner Führungskraft die Messergebnisse und gab ihr Zugang zu den Quellcodes. In der Folge wurden 2 von 14 Projekten für die neuen Messungen angepasst, und da diese beiden Projekte dafür ausgelegt waren, riesige Excel-Tabellen zu verarbeiten, war das Ergebnis mehr als signifikant für das Produkt.

Ich präsentiere Ihnen zudem Messungen für verschiedene Versionen von .NET Core, damit jeder von Ihnen die optimale Lösung wählen kann. Ich möchte nur hinzufügen, dass wir in dem Unternehmen, in dem ich arbeite, ToUpper() für den Vergleich von Zeichenfolgen verwenden.

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();
        }

Leistung in .NET Core

Leistung in .NET Core

In .NET Core 3.0 beträgt der Anstieg für jede dieser Methoden ~x2 und balanciert die Implementierungen miteinander.

Leistung in .NET Core

Leistung in .NET Core

Tier-Kompilierung

In meinem vorherigen Artikel habe ich dieses Feature kurz umrissen, und ich möchte meine Aussagen erweitern und ergänzen. Die mehrstufige Kompilierung beschleunigt die Startzeit Ihrer Lösung, kann jedoch dazu führen, dass Teile Ihres Codes in einer optimierten Version im Hintergrund kompiliert werden, was zu leichten Overheads führen kann. Mit dem Erscheinen von .NET Core 3.0 wurde die Build-Zeit für Projekte mit aktivierter tier compilation verkürzt, und Bugs, die mit dieser Technologie zusammenhingen, wurden behoben. Früher führte diese Technologie zu Fehlern bei den ersten Anfragen in ASP.NET Core und zu Hängern bei der ersten Kompilierung im Modus der mehrstufigen Kompilierung. Momentan ist sie in .NET Core 3.0 standardmäßig aktiviert, kann jedoch auf Wunsch deaktiviert werden. Wenn Sie Teamleiter, Senior, Mid-Level oder Abteilungsleiter sind, sollten Sie verstehen, dass eine schnelle Projektentwicklung den Wert Ihres Teams steigert, und diese Technologie wird Ihnen sowohl Entwicklerzeit als auch insgesamt Projektzeit sparen.

.NET Level Up

Aktualisieren Sie Ihre Version des .NET Framework / .NET Core. Oft bringt jede neue Version einen zusätzlichen Leistungsgewinn und neue Funktionen mit sich.

Welche konkreten Vorteile gibt es? Lassen Sie uns einige davon betrachten:

  • In .NET Core 3.0 wurden R2R-Images eingeführt, die die Startzeit von .NET Core-Anwendungen reduzieren.
  • Mit der Version 2.2 wurde die Tier Compilation eingeführt, wodurch Programmierer weniger Zeit für den Start eines Projekts benötigen.
  • Unterstützung neuer .NET Standard-Versionen.
  • Unterstützung der neuen Version der Programmiersprache.
  • Optimierungen: Mit jeder neuen Version verbessern sich die grundlegenden Bibliotheken wie Collection/Struct/Stream/String/Regex und vieles mehr. Wenn Sie von .NET Framework auf .NET Core umsteigen, profitieren Sie sofort von einer erheblichen Leistungssteigerung. Hier ist ein Link zu den Optimierungen, die in .NET Core 3.0 hinzugefügt wurden: https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-core-3-0/

Leistung in .NET Core

Fazit

Bei der Programmierung sollten Sie verschiedene Aspekte Ihres Projekts berücksichtigen und die Funktionen Ihrer Programmiersprache und Plattform nutzen, um die besten Ergebnisse zu erzielen. Ich würde mich freuen, wenn Sie Ihr Wissen über Optimierungen in .NET teilen könnten.

Link zu GitHub

Quelle: habr.com

Erwerben Sie zuverlässiges Hosting für Websites mit DDoS-Schutz, VPS VDS-Server 🔥 Kaufen Sie zuverlässiges Hosting für Websites mit DDoS-Schutz, VPS VDS-Server | ProHoster