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 .
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:


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: . 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):


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:

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


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


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:

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.
Quelle: habr.com
