Leistung in .NET Core

Hallo allerseits! Dieser Artikel ist eine Sammlung von Best Practices, die ich und meine Kollegen seit langem bei der Arbeit an verschiedenen Projekten verwenden.
Angaben zu der Maschine, auf der 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), 64-Bit-RyuJIT
Kern: .NET Core 2.2.7 (CoreCLR 4.6.28008.02, CoreFX 4.6.28008.03), 64-Bit-RyuJIT
[Host]: .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), 64-Bit-RyuJIT
Core: .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), 64-Bit-RyuJIT
Job = Core Runtime = Core
ToList vs ToArray und Zyklen
Ich hatte vor, diese Informationen mit der Veröffentlichung von .NET Core 3.0 vorzubereiten, aber sie haben mich überholt. Ich möchte nicht den Ruhm eines anderen stehlen und die Informationen eines anderen kopieren. Ich möchte daher nur auf einen
Link zu einem guten Artikel verweisen,
in dem der Vergleich ausführlich beschrieben wird .
Von mir aus möchte ich Ihnen nur meine Messungen und Ergebnisse vorstellen. Für Liebhaber des „C ++ - Stils“ von Schreibschleifen habe ich umgekehrte Schleifen hinzugefügt.
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; } }
Die Leistung in .NET Core 2.2 und 3.0 ist nahezu identisch. Folgendes habe ich in .NET Core 3.0 erreicht:


Wir können daraus schließen, dass die Schleifenverarbeitung einer Sammlung vom Typ Array aufgrund ihrer internen Optimierungen und der expliziten Zuweisung der Größe der Sammlung schneller ist. Beachten Sie auch, dass eine Auflistung vom Typ Liste ihre Vorteile hat und Sie die gewünschte Auflistung je nach den erforderlichen Berechnungen verwenden sollten. Auch wenn Sie die Logik des Arbeitens mit Zyklen schreiben, vergessen Sie nicht, dass dies eine gewöhnliche Schleife ist und auch einer möglichen Optimierung von Zyklen unterliegt. Ein Artikel erschien lange auf habr:
https://habr.com/en/post/124910/ . Es ist immer noch relevant und wird zum Lesen empfohlen.
Werfen
Vor einem Jahr arbeitete ich in einem Unternehmen an einem Legacy-Projekt. In diesem Projekt lag es im Rahmen des Normalen, die Feldvalidierung über ein Try-Catch-Throw-Konstrukt durchzuführen. Ich habe bereits verstanden, dass dies eine ungesunde Geschäftslogik des Projekts ist, daher habe ich versucht, ein solches Design möglichst nicht zu verwenden. Aber mal sehen, was der schlechte Ansatz ist, um Fehler mit einem solchen Design zu behandeln. Ich habe einen kleinen Code geschrieben, um die beiden Ansätze zu vergleichen und die „Bänke“ für jede Option zu fotografieren.
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 haben ein ähnliches Ergebnis (.NET Core 3.0):


Try catch erschwert das Verständnis des Codes und verlängert die Ausführungszeit Ihres Programms. Wenn Sie diese Konstruktion benötigen, sollten Sie keine Codezeilen einfügen, von denen keine Fehlerbehandlung erwartet wird. Dies erleichtert das Verständnis des Codes. Tatsächlich wird das System nicht so sehr durch die Ausnahmebehandlung geladen, sondern vielmehr durch das neue Ausnahmekonstrukt.
Das Auslösen von Ausnahmen ist langsamer als jede Klasse, die einen Fehler im gewünschten Format sammelt. Wenn Sie ein Formular oder Daten verarbeiten und offensichtlich wissen, welcher Fehler vorliegen sollte, warum sollten Sie ihn dann nicht verarbeiten?
Sie sollten nicht throw new Exception () schreiben, wenn diese Situation nicht außergewöhnlich ist.
Das Behandeln und Auslösen einer Ausnahme ist sehr teuer !!!ToLower, ToLowerInvariant, ToUpper, ToUpperInvariant
Während seiner 5-jährigen Erfahrung auf der .NET-Plattform hat er viele Projekte kennengelernt, die String Matching verwendeten. Ich sah auch das folgende Bild: Es gab eine Enterprise-Lösung mit vielen Projekten, die jeweils unterschiedliche Zeichenfolgenvergleiche durchführten. Aber was lohnt sich und wie kann man es vereinheitlichen? In Richters CLR über C # habe ich gelesen, dass ToUpperInvariant () schneller ist als ToLowerInvariant ().
Ausschnitt aus dem Buch:

Natürlich habe ich es nicht geglaubt und mich damals entschlossen, einige Tests auf .NET Framework durchzuführen, und das Ergebnis hat mich schockiert - mehr als 15% mehr Leistung. Als ich am nächsten Morgen zur Arbeit kam, zeigte ich meinen Vorgesetzten diese Messungen und gab ihnen Zugang zur Quelle. Danach wurden 2 von 14 Projekten für neue Messungen geändert. Angesichts der Tatsache, dass es diese beiden Projekte gab, um große Excel-Tabellen zu verarbeiten, war das Ergebnis für das Produkt mehr als signifikant.
Ich präsentiere Ihnen auch Messungen für verschiedene Versionen von .NET Core, damit jeder von Ihnen eine Auswahl in Richtung der optimalsten Lösung treffen kann. Und ich möchte nur hinzufügen, dass wir in der Firma, in der ich arbeite, ToUpper () verwenden, um Zeichenfolgen zu vergleichen.
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(); }
; 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(); }


In .NET Core 3.0 beträgt der Gewinn für jede dieser Methoden ~ x2 und gleicht die Implementierungen untereinander aus.


Stufenzusammenstellung
In meinem letzten Artikel habe ich diese Funktionalität kurz beschrieben, ich möchte meine Worte korrigieren und ergänzen. Die mehrstufige Kompilierung beschleunigt die Startzeit Ihrer Lösung, Sie opfern jedoch Teile Ihres Codes, um eine optimierte Version im Hintergrund zu kompilieren, was zu einem geringen Overhead führen kann. Mit dem Aufkommen von NET Core 3.0 verringerte sich die Erstellungszeit von Projekten mit aktivierter Tier-Kompilierung und es wurden Fehler im Zusammenhang mit dieser Technologie behoben. Bisher führte diese Technologie zu Fehlern bei den ersten Anforderungen in ASP.NET Core und zum Einfrieren beim ersten Erstellen im mehrstufigen Kompilierungsmodus. Derzeit ist es in .NET Core 3.0 standardmäßig aktiviert, Sie können es jedoch nach Bedarf deaktivieren. Wenn Sie in der Position eines Teamleiters, eines leitenden Mitarbeiters oder eines Abteilungsleiters tätig sind, müssen Sie verstehen, dass die rasche Entwicklung des Projekts den Wert des Teams erhöht und dass Sie mit dieser Technologie sowohl Zeit für Entwickler als auch für das Projekt sparen können.
.NET-Level auf
Aktualisieren Sie Ihr .NET Framework / .NET Core. Oft bietet jede neue Version einen zusätzlichen Leistungsschub und fügt neue Funktionen hinzu.
Aber was genau sind die Vorteile? Schauen wir uns einige an:
- In .NET Core 3.0 wurden R2R-Images eingeführt, die die Startzeit von .NET Core-Anwendungen verkürzen.
- Mit Version 2.2 wurde Tier Compilation eingeführt, dank derer Programmierer weniger Zeit damit verbringen, ein Projekt zu starten.
- Unterstützung für den neuen .NET Standard.
- Unterstützung für die neue Version der Programmiersprache.
- Die Optimierung wird mit jeder neuen Version durch die Basisbibliotheken Collection / Struct / Stream / String / Regex und vieles mehr verbessert. Wenn Sie ein Upgrade von .NET Framework auf .NET Core durchführen, wird die Leistung sofort erheblich gesteigert. Ich füge beispielsweise einen Link zu einem Teil der Optimierungen hinzu, die in .NET Core 3.0 hinzugefügt wurden: https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-core-3-0/

Fazit
Beim Schreiben von Code sollten Sie verschiedene Aspekte Ihres Projekts berücksichtigen und die Funktionen Ihrer Programmiersprache und Plattform verwenden, um das beste Ergebnis zu erzielen. Ich würde mich freuen, wenn Sie Ihre Kenntnisse im Zusammenhang mit der Optimierung in .NET teilen.
Github Link