Profilierung von C#-Code: Engpässe in der Praxis finden
\nDie Leistungsoptimierung ist eine der Kernaufgaben eines Entwicklers. Selbst der schönste Code kann langsam laufen, wenn er Engpässe enthält. In diesem Artikel werden wir uns ansehen, wie man C#-Code in der .NET-Umgebung profiliert, problematische Stellen findet und behebt. Sie lernen praktische Werkzeuge wie BenchmarkDotNet, dotMemory und die integrierte Stopwatch kennen und erhalten fertige Beispiele zur Analyse.
Warum ist Profilierung wichtig?
\nProfilierung ist nicht einfach nur das Starten einer Anwendung und das Messen der Zeit. Es ist ein systematischer Ansatz zur Identifizierung von „Flaschenhälsen“: langsamen Algorithmen, Speicherlecks, suboptimalen Datenbankabfragen oder häufigen Heap-Allokationen. Ohne Profilierung riskieren Sie, Stunden mit der Optimierung von Bereichen zu verbringen, die ohnehin schnell arbeiten, während Sie die eigentlichen Probleme übersehen.
\n\n1. Profilierung mit Stopwatch (Schnellstart)
\nDer einfachste Weg, die Ausführungszeit von Code zu messen, ist die Verwendung der Klasse System.Diagnostics.Stopwatch. Sie eignet sich für eine erste Einschätzung, aber aufgrund des Einflusses der JIT-Kompilierung und von Hintergrundprozessen nicht für präzise Benchmarks.
Beispiel für die Verwendung von Stopwatch
\nusing System.Diagnostics;\n\npublic class Example\n{\n public void Run()\n {\n Stopwatch sw = Stopwatch.StartNew();\n\n // Zu messender Code\n SimulateWork();\n\n sw.Stop();\n Console.WriteLine($"Ausführungszeit: {sw.ElapsedMilliseconds} ms");\n }\n\n private void SimulateWork()\n {\n for (int i = 0; i < 1_000_000; i++)\n {\n // Arbeitsimulation\n }\n }\n}\nNachteile: Stopwatch berücksichtigt kein JIT-Warming, keine Garbage Collection und andere Faktoren. Verwenden Sie für ernsthafte Analysen spezialisierte Bibliotheken.
\n\n2. Professionelle Profilierung mit BenchmarkDotNet
\nBenchmarkDotNet ist der Goldstandard für Benchmarking in .NET. Es wärmt den Code automatisch auf, führt mehrere Iterationen durch, analysiert Statistiken und berücksichtigt den Einfluss der Garbage Collection. Installieren Sie das Paket über NuGet:
dotnet add package BenchmarkDotNet\n\nBeispiel für einen Benchmark zum Vergleich zweier Methoden
\nusing BenchmarkDotNet.Attributes;\nusing BenchmarkDotNet.Running;\n\npublic class StringBenchmark\n{\n private string[] data = { "apple", "banana", "cherry", "date" };\n\n [Benchmark]\n public string ConcatWithPlus()\n {\n string result = "";\n foreach (var s in data)\n result += s;\n return result;\n }\n\n [Benchmark]\n public string ConcatWithStringBuilder()\n {\n var sb = new System.Text.StringBuilder();\n foreach (var s in data)\n sb.Append(s);\n return sb.ToString();\n }\n}\n\npublic class Program\n{\n public static void Main()\n {\n var summary = BenchmarkRunner.Run<StringBenchmark>();\n }\n}\nNach dem Start sehen Sie eine Tabelle mit Zeit, Speicherallokationen und Fehlern. Dies ermöglicht einen objektiven Vergleich von StringBuilder und Verkettung mit +.
3. Speicheranalyse mit dotMemory und Visual Studio Diagnostic Tools
\nEngpässe treten nicht nur bei der Geschwindigkeit auf, sondern auch beim Speicherverbrauch. Das Tool dotMemory von JetBrains und die integrierten Tools von Visual Studio (Diagnostic Tools) helfen dabei, Speicherlecks, häufige Allokationen und Objekte zu finden, die lange im Heap leben.
So verwenden Sie die Visual Studio Diagnostic Tools
\n- \n
- Starten Sie die Anwendung im Debug-Modus. \n
- Öffnen Sie das Fenster Diagnostic Tools (Debug → Windows → Show Diagnostic Tools). \n
- Aktivieren Sie die Profilierung des Speichers (Memory Usage) oder der CPU (CPU Usage). \n
- Erstellen Sie einen Speicher-Snapshot (Snapshot) vor und nach der Ausführung des Codes.