Leistung in .NET Core

Leistung in .NET Core

Leistung in .NET Core

Hallo zusammen! Dieser Artikel ist eine Sammlung von Best Practices, die ich und meine Kollegen über einen langen Zeitraum hinweg bei der Arbeit an verschiedenen Projekten angewendet haben.

Informationen über das Gerät, auf dem die Berechnungen durchgeführt wurden:BenchmarkDotNet=v0.11.5, OS=Windows 10.0.18362
Intel Core i5-8250U CPU 1.60GHz (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


Diese Informationen wollte ich mit dem Release von .NET Core 3.0 vorbereiten, aber ich wurde überholt. Ich möchte mir nicht den Ruhm anderer stehlen und ihre Informationen kopieren, also gebe ich einfach an einen Link zu einem guten Artikel, in dem der Vergleich ausführlich beschrieben wird..

Von meiner Seite möchte ich Ihnen nur meine Messungen und Ergebnisse vorstellen. Ich habe darin Rückwärts-Schleifen für Liebhaber des „C++-Stils“ beim Schreiben von Schleifen 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 Leistung in .NET Core 2.2 und 3.0 ist nahezu identisch. Das habe ich in .NET Core 3.0 erreicht:

Leistung in .NET Core

Leistung in .NET Core

Wir können zu dem Schluss kommen, dass die zyklische Verarbeitung von Arrays aufgrund ihrer internen Optimierungen und der klaren Definition der Sammlungsgröße schneller ist. Es ist auch wichtig zu beachten, dass eine Liste ihre eigenen Vorteile hat und Sie die entsprechende Sammlung je nach den erforderlichen Berechnungen verwenden sollten. Selbst wenn Sie die Logik für Schleifen schreiben, sollten Sie nicht vergessen, dass es sich um eine gewöhnliche Schleife handelt, die ebenfalls für potenzielle Optimierungen anfällig ist. Vor einiger Zeit wurde auf Habr ein Artikel veröffentlicht: https://habr.com/ru/post/124910/. Er ist immer noch aktuell und wird zum Lesen empfohlen.

Throw

Vor einem Jahr arbeitete ich in einem Unternehmen an einem Legacy-Projekt, in dem es im Rahmen des Normals vorkam, die Validierung von Feldern über eine try-catch-throw-Konstruktion zu behandeln. Ich wusste damals schon, dass dies eine ungesunde Geschäftslogik für das Projekt war, also versuchte ich, diese Konstruktion nach Möglichkeit nicht zu verwenden. Lassen Sie uns jedoch untersuchen, was an dem Ansatz, Fehler auf diese Weise zu behandeln, schlecht 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 sind vergleichbar (.NET Core 3.0):

Leistung in .NET Core

Leistung in .NET Core

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, von denen keine Fehlerbehandlung erwartet wird - das erleichtert das Verständnis des Codes. Tatsächlich belastet die Behandlung von Ausnahmen nicht so sehr das System, wie das Werfen der Fehler selbst über die Konstruktion throw new Exception.

Das Werfen von Ausnahmen ist langsamer als jede Art von Klasse, die den Fehler im gewünschten Format sammelt. Wenn Sie ein Formular oder Daten verarbeiten und genau wissen, welcher Fehler auftreten sollte, warum sollten Sie ihn dann nicht behandeln?

Es ist nicht sinnvoll, die Konstruktion throw new Exception() zu verwenden, wenn diese Situation keine Ausnahme darstellt. Die Behandlung und das Werfen von Ausnahmen sind sehr kostspielig!!!

ToLower, ToLowerInvariant, ToUpper, ToUpperInvariant

In meinen 5 Jahren Erfahrung auf der .NET-Plattform habe ich viele Projekte gesehen, die Zeichenfolgenvergleiche nutzten. Ich habe auch folgendes Bild gesehen: es gab eine Enterprise-Lösung mit vielen Projekten, die jeweils den Zeichenfolgenvergleich unterschiedlich durchführten. Aber was sollte man verwenden und wie kann man das vereinheitlichen? In dem Buch CLR via C# von Richter las ich die Information, dass die Methode ToUpperInvariant() schneller als ToLowerInvariant() arbeitet.

Auszug aus dem Buch:

Leistung in .NET Core

Natürlich glaubte ich das nicht und beschloss, einige Tests damals noch auf .NET Framework durchzuführen und das Ergebnis schockierte mich – über 15 % Leistungssteigerung. Am nächsten Morgen bei der Arbeit zeigte ich die Messdaten meinem Vorgesetzten und stellte ihm die Quellcodes zur Verfügung. Daraufhin wurden 2 von 14 Projekten für neue Messungen angepasst, und angesichts dessen, dass diese beiden Projekte existierten, um riesige Excel-Tabellen zu verarbeiten, war das Ergebnis mehr als bedeutend für das Produkt.

Ich präsentiere Ihnen auch Messungen für verschiedene Versionen von .NET Core, damit jeder von Ihnen eine Wahl für die optimalste Lösung treffen kann. Ich möchte nur hinzufügen, dass wir in der Firma, in der ich arbeite, ToUpper() für den Zeichenfolgenvergleich 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 Gewinn für jede dieser Methoden ~x2 und balanciert die Implementierungen untereinander.

Leistung in .NET Core

Leistung in .NET Core

Tier Compilation

In meinem vorherigen Artikel habe ich diese Funktionalität kurz beschrieben und möchte nun meine Aussagen korrigieren und erweitern. Die mehrstufige Kompilierung beschleunigt die Startzeit Ihrer Lösung, aber Sie opfern dabei, dass Teile Ihres Codes im Hintergrund in eine optimierte Version kompiliert werden, was zu geringen Overheads führen kann. Mit der Einführung von .NET Core 3.0 wurde die Build-Zeit von Projekten mit aktivierter tier compilation verringert und Fehler, die mit dieser Technologie verbunden waren, wurden behoben. Früher führte diese Technologie zu Fehlern bei den ersten Anfragen in ASP.NET Core und zu Verzögerungen bei der ersten Kompilierung im mehrstufigen Kompilierungsmodus. Derzeit ist sie in .NET Core 3.0 standardmäßig aktiviert, aber Sie können sie bei Bedarf deaktivieren. Wenn Sie in der Position eines Teamleiters, Senior, Middle oder Abteilungsleiters sind, sollten Sie verstehen, dass eine schnelle Projektentwicklung den Wert des Teams erhöht und diese Technologie Ihnen hilft, sowohl für die Entwickler als auch für die Arbeitszeit des Projekts Zeit zu sparen.

.NET Level Up

Erhöhen Sie die Version Ihres .NET Framework / .NET Core. Oft bietet jede neue Version einen zusätzlichen Leistungsschub und fügt neue Funktionen hinzu.

Aber welche Vorteile sind das genau? Lassen Sie uns einige davon betrachten:

  • In .NET Core 3.0 gibt es R2R-Images, die die Startzeit von .NET Core-Anwendungen verkürzen werden.
  • Ab Version 2.2 gibt es Tier Compilation, die es Entwicklern ermöglicht, weniger Zeit mit dem Start des Projekts zu verbringen.
  • Unterstützung neuer Standards .NET Standard.
  • Unterstützung der neuen Version der Programmiersprache.
  • Optimierung, mit jeder neuen Version wird die Optimierung der grundlegenden Bibliotheken Collection/Struct/Stream/String/Regex und viele andere Dinge verbessert. Wenn Sie von .NET Framework auf .NET Core umsteigen, erhalten Sie sofort einen erheblichen Leistungsschub. Zum Beispiel füge ich einen Link zu einem Teil der Optimierungen an, 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

Beim Schreiben von Code sollten Sie verschiedenen Aspekten Ihres Projekts Aufmerksamkeit schenken und die Funktionen Ihrer Programmiersprache und Plattform nutzen, um das beste Ergebnis zu erzielen. Ich würde mich freuen, wenn Sie Ihr Wissen über Optimierung in .NET mit mir teilen.

Link zu GitHub

Quelle: habr.com

60GB SSD 8Gb DDR4