Diese Seite kann automatisch übersetzten Text enthalten.
C# Native AOT-Leistung
Wie schnell sind .NET Native AOT-Anwendungen im Vergleich zum regulären verwalteten Code? Kann AOT JIT übertreffen? Wie werden Native AOT-Anwendungen verglichen?
Dieser Artikel ist Teil einer Serie über Native AOT in .NET. Wenn Sie mit Native AOT nicht vertraut sind, lesen Sie zuerst den Teil So entwickeln Sie Native AOT-Anwendungen in .NET.
Dieser Artikel vergleicht die Leistung von .NET und Native AOT. Zunächst werden wir die offiziellen Microsoft-Benchmarks überprüfen. Sie ermöglichen den Vergleich verschiedener .NET-Bereitstellungsoptionen für einfache ASP.NET-Anwendungen.
Anschließend lernen Sie, wie Sie mit BenchmarkDotNet und Hyperfine-Tools eigene Benchmarks ausführen. Mit solchen Benchmarks können Sie die Codegeschwindigkeit in Ihrer Umgebung messen.
ASP.NET-Benchmarks
Das ASP.NET-Team unterhält eine solide Infrastruktur für Leistungstests. Sie testen verschiedene Szenarien in unterschiedlichen Umgebungen.
Am meisten interessieren uns die Native AOT-Benchmarks. Die primäre Informationsquelle ist das folgende PowerBI-Dashboard. Die Daten dort basieren auf 3 „Walen“: Testanwendungen, Bereitstellungsszenarien und Metriken.
Testanwendungen
Den Quellcode von Benchmarks und Testanwendungen finden Sie im Repository aspnet/Benchmarks.
Native AOT-Benchmarks vergleichen 3 Anwendungstypen:
- Stage1 - eine minimale API basierend auf HTTP und JSON. Der Quellcode der Anwendung befindet sich
in
/src/BenchmarksApps/BasicMinimalApi
. - Stage1Grpc - eine ähnliche API basierend auf gRPC (
/src/BenchmarksApps/Grpc/BasicGrpc
) - Stage2 - vollständige Webanwendung mit Datenbank, Authentifizierung
(
/src/BenchmarksApps/TodosApi
)
.NET-Bereitstellungsszenarien
Testanwendungen werden in unterschiedlichen Umgebungen ausgeführt. Derzeit verwenden Benchmarks virtuelle Windows- und Linux-Maschinen mit 28 Kernen. Es gibt auch separate Linux-Umgebungen für ARM- und Intel-Prozessoren.
Anwendungen werden auch in verschiedenen Konfigurationen getestet. Eine Anwendung in einer bestimmten Konfiguration definiert ein „Szenario“.
Sie können die Strg-Taste (oder ⌘) gedrückt halten, um mehrere Szenarien oder Umgebungen auf dem PowerBI-Dashboard auszuwählen.
Metriken
Benchmarks erfassen grundlegende Metriken für jede bereitgestellte Anwendung. Tests messen beispielsweise die Anzahl der Anfragen pro Sekunde (RPS), die Startzeit und den maximalen Arbeitsspeicher.
Dadurch können wir Metrikwerte für verschiedene Konfigurationen derselben Anwendung vergleichen.
Leistungsvergleich
Wir werden StageX-Szenarien mit StageXAot und StageXAotSpeedOpt vergleichen. Sie verwenden die folgende Konfiguration:
Szenario | dotnet publish -Build-Argumente |
---|---|
StageX | PublishAot=false EnableRequestDelegateGenerator=false |
Stage2Aot | PublishAot=true StripSymbols=true |
Stage2AotSpeedOpt | PublishAot=true StripSymbols=true OptimizationPreference=Speed |
Alle oben genannten Szenarien verwenden auch die Umgebungsvariable DOTNET_GCDynamicAdaptationMode=1
.
StageXAotSpeedOpt-Szenarien ermöglichen es, die Auswirkungen der Einstellung OptimizationPreference = Speed abzuschätzen.
Sie können auch StageXTrimR2RSingleFile-Szenarien überprüfen. Solche Szenarien entsprechen einer getrimmten ReadyToRun-Bereitstellung, einer anderen Form der Ahead-of-Time-Kompilierung in .NET. Manchmal ist es eine gute Alternative zu Native AOT.
Hier sind die aktuellen Leistungsvergleichsergebnisse für den .NET 9 Release Candidate (September 2024):
Startzeit
AOT-Anwendungen starten viel schneller als verwaltete Versionen. Das gilt sowohl für Stage1- als auch für Stage2-Anwendungen und für alle Umgebungen. Beispielergebnisse:
Szenario | Startzeit (ms) |
---|---|
Stage2AotSpeedOpt | 100 |
Stage2Aot | 109 |
Stage2 | 528 |
Arbeitssatz
Der maximale Arbeitssatz für Native AOT-Anwendungen ist kleiner als für verwaltete Versionen. Unter Linux verwenden verwaltete Versionen etwa 1,5- bis 2-mal mehr RAM als AOT-Versionen. Beispiel:
Szenario | Max. Arbeitssatz (MB) |
---|---|
Stage1Aot | 56 |
Stage1AotSpeedOpt | 57 |
Stage1 | 126 |
Unter Windows ist der Unterschied geringer. Insbesondere für Stage2:
Szenario | Max. Arbeitssatz (MB) |
---|---|
Stage2Aot | 152 |
Stage2AotSpeedOpt | 150 |
Stage2 | 167 |
Anfragen pro Sekunde
Höhere RPS-Werte bedeuten eine schnellere Anwendung. Die leichte Stage1-Anwendung verarbeitet normalerweise etwa 800–900.000 Anfragen pro Sekunde. Die größere Stage2-Anwendung verarbeitet nur etwa 200.000 Anfragen.
Für die Stage2-Anwendung verarbeitet die .NET-Version in allen Umgebungen mehr Anfragen als die AOT-Versionen. Die Geschwindigkeit der Stage2AotSpeedOpt-Version ist manchmal ähnlich. Normalerweise liegt sie jedoch zwischen Stage2 und Stage2Aot. Hier sind die typischen Ergebnisse:
Szenario | RPS |
---|---|
Stage2 | 235.008 |
Stage2AotSpeedOpt | 215.637 |
Stage2Aot | 194.264 |
Die Ergebnisse für die Stage1-Anwendung sind unter Intel Linux und Intel Windows ähnlich. Unter Ampere Linux schlägt AOT jedoch die verwaltete Version. Beispielergebnisse von Ampere Linux:
Szenario | RPS |
---|---|
Stage1AotSpeedOpt | 929.524 |
Stage1Aot | 912.344 |
Stage1 | 844.659 |
Die Umgebung und der Anwendungscode können die Geschwindigkeit also erheblich beeinflussen. Es ist sinnvoll, eigene Benchmarks auszuführen, um die Vorteile von Native AOT für Ihr Projekt abzuschätzen. Schreiben wir benutzerdefinierte Benchmarks ohne die Testinfrastruktur von Microsoft.
Benchmarking von Native AOT-Anwendungen
Wir werden 2 Arten von Benchmarks verwenden. Der erste basiert auf BenchmarkDotNet – der beliebten Bibliothek zum Benchmarking von .NET-Code. Diese Benchmarks vergleichen die reine Geschwindigkeit, ohne Startzeit.
Der zweite basiert auf dem Tool hyperfine. Es ermöglicht den Vergleich der Ausführungszeit von zwei Shell-Befehlen. Diese Benchmarks vergleichen die Gesamtgeschwindigkeit, einschließlich der Startzeit.
Wir werden hier den Speicherverbrauch nicht vergleichen. Derzeit unterstützt der
NativeMemoryProfiler
-Diagnostiker in BenchmarkDotNet die Native AOT-Laufzeit nicht. Hyperfine
verfolgt derzeit auch nicht den Speicherverbrauch.
Sie können den Quellcode aus dem NativeAotBenchmarks-Repository auf GitHub herunterladen. Wir empfehlen Ihnen, sie in Ihrer Umgebung auszuprobieren. Dieser Artikel beschreibt die Ergebnisse eines Windows 11-Laptops mit Intel Core i9-13900H-Prozessor und 16 GB RAM.
Stellen Sie sicher, dass Sie Benchmarks richtig ausführen. Hier sind die allgemeinen Empfehlungen:
- Verwenden Sie den Release-Build.
- Schalten Sie alle Anwendungen außer dem Benchmark-Prozess aus. Deaktivieren Sie beispielsweise Antivirensoftware, schließen Sie Visual Studio und einen Webbrowser.
- Lassen Sie Ihren Laptop angeschlossen und verwenden Sie den Modus mit der besten Leistung.
- Verwenden Sie in den zu vergleichenden Szenarien dieselben Eingabedaten.
Testfälle
Wir werden 2 Szenarien in .NET 8 vergleichen:
1. Einfacher C#-Code für eine Zeichenfolgenkomprimierung unter Verwendung der Anzahl wiederholter Zeichen. Beispielsweise würde die Zeichenfolge „aabcccccaaa“ zu „a2b1c5a3“ werden:
string Compress(string s)
{
StringBuilder compressed = new(s.Length);
for (int i = 0; i < s.Length; ++i)
{
char c = s[i];
for (int j = i + 1; j <= s.Length; ++j)
{
if (j == s.Length || s[j] != c)
{
compressed.Append(c + $"{j - i}");
i = j - 1;
if (compressed.Length > s.Length)
return s;
break;
}
}
}
if (compressed.Length <= s.Length)
return compressed.ToString();
return s;
}
2. Eine anspruchsvollere PDF-zu-PNG-Konvertierungsaufgabe, die Docotic.Pdf verwendet.
Voraussetzungen
Installieren Sie Voraussetzungen für die .NET Native AOT-Bereitstellung.
Installieren Sie Hyperfine, um entsprechende Benchmarks auszuführen.
Für PDF-zu-PNG-Benchmarks erhalten Sie auf der Seite Laden Sie die C# .NET PDF-Bibliothek herunter einen kostenlosen zeitlich
begrenzten Lizenzschlüssel. Sie müssen den Lizenzschlüssel in Helper.cs
anwenden.
BenchmarkDotNet
Diese Benchmarks befinden sich im Projekt NativeAotBenchmarks
. Wir vergleichen die Ergebnisse für
RuntimeMoniker.NativeAot80 und RuntimeMoniker.Net80. Standardmäßig erstellt BenchmarkDotNet Native
AOT-Code mit der Einstellung OptimizationPreference=Speed
.
BenchmarkDotNet führt 6 oder mehr Aufwärmiterationen durch. Das hilft JIT, Code vorzukompilieren und einige Statistiken zu sammeln. Daher schließen solche Benchmarks die Startzeit vom Vergleich aus.
Stringkomprimierung
Der CompressString
-Benchmark für die String-Komprimierung verwendet einen langen String mit
doppelten Zeichen. Der häufigste Fehler wäre, einen zufälligen String zu generieren. In einem
solchen Fall würden Benchmarks für Native AOT und .NET 8 unterschiedliche Eingabestrings verwenden.
Es ist möglich, zufällige Strings zu verwenden, aber Sie müssen einen Zufallsgenerator mit demselben
Seed initialisieren.
Die Native AOT-Version läuft etwa 1,08-mal schneller als die .NET 8-Version:
Method | Runtime | Mean | Error | StdDev |
---|---|---|---|---|
Compress | .NET 8.0 | 4.117 ms | 0.0553 ms | 0.0517 ms |
Compress | NativeAOT 8.0 | 3.809 ms | 0.0403 ms | 0.0377 ms |
PDF zu PNG
PDF-zu-PNG-Benchmarks verarbeiten PDF-Dokumente im Speicher. Dadurch kann die Interaktion mit dem Dateisystem ausgeschlossen werden. I/O-Operationen mit einer Festplatte können die Benchmark-Ergebnisse verfälschen.
Wir testen die Geschwindigkeit mit zwei PDF-Dokumenten. Das erste, Banner Edulink One.pdf, ist komplexer. Es wird in ein PNG mit 72 dpi konvertiert und benötigt mehr Zeit für die Verarbeitung. Die .NET 8-Version ist für dieses Dokument etwas schneller:
Method | Runtime | Mean | Error | StdDev |
---|---|---|---|---|
Convert | .NET 8.0 | 1.103 s | 0.0156 s | 0.0146 s |
Convert | NativeAOT 8.0 | 1.167 s | 0.0160 s | 0.0149 s |
Das zweite Dokument ist kleiner und einfacher. Es wird in ein PNG mit 300 dpi konvertiert. Und die Geschwindigkeit ist fast gleich:
Method | Runtime | Mean | Error | StdDev |
---|---|---|---|---|
Convert | .NET 8.0 | 290.1 ms | 5.78 ms | 6.88 ms |
Convert | NativeAOT 8.0 | 288.3 ms | 4.44 ms | 3.94 ms |
hyperfine
Diese Benchmarks befinden sich im Projekt NativeAotTestApp
. Das Projekt verwendet nicht die
Einstellung OptimizationPreference=Speed
. Sie können sie in NativeAotTestApp.csproj aktivieren:
<OptimizationPreference>Speed</OptimizationPreference>
Verwenden Sie das Skript benchmark.bat, um Tests unter Windows auszuführen. Sie können es für
Unix/Linux-basierte Betriebssysteme in Bash konvertieren. Das Skript erstellt .NET 8- und Native
AOT-Versionen derselben App. Anschließend vergleicht es ihre Leistung mit ähnlichen Befehlen:
hyperfine --warmup 3 "net8-app.exe" "native-aot-app.exe"
Warmup-Läufe in Hyperfine helfen dabei, Testanwendungen auf „warmen“ Festplatten-Caches zu starten. Anders als BenchmarkDotNet hilft das Hyperfine-Warmup nicht bei JIT. Daher vergleichen Hyperfine-Benchmarks die Gesamtgeschwindigkeit der Anwendung, einschließlich der Startzeit.
Unsere Testanwendung unterstützt das Argument der Iterationsanzahl. Es ermöglicht, denselben Code mehrmals in einer einfachen Schleife zu wiederholen:
for (int i = 0; i < iterationCount; ++i)
CompressString(args);
Die Idee besteht darin, die Auswirkungen des Zeitunterschieds beim Start zu verringern. Durch die Wiederholung desselben Codes erhält JIT die Möglichkeit, mehr Laufzeitstatistiken zu sammeln und schnelleren Code zu generieren.
Eine häufige Situation ist die folgende. Beim ersten Mal führen Sie Benchmarks mit einer einzigen Iteration aus. Eine Native AOT-Version arbeitet viel schneller. Dann führen Sie dieselben Benchmarks mit mehreren Iterationen aus und die Gesamtgeschwindigkeit beider Versionen wird gleich. Das bedeutet, dass eine verwaltete Version nach dem Start tatsächlich schneller ist.
Stringkomprimierung
Bei 100.000 Iterationen derselben Eingabezeichenfolgenkomprimierung ist die Leistung von Native AOT besser:
Benchmark 1: .NET 8 version (100000 iterations)
Time (mean ± σ): 151.5 ms ± 2.6 ms [User: 32.1 ms, System: 1.6 ms]
Range (min … max): 148.0 ms … 157.5 ms 19 runs
Benchmark 2: Native AOT version (100000 iterations)
Time (mean ± σ): 55.1 ms ± 3.1 ms [User: 15.0 ms, System: 2.1 ms]
Range (min … max): 51.6 ms … 65.9 ms 51 runs
Summary
Native AOT version ran 2.75 ± 0.16 times faster than .NET 8 version
Aber die Geschwindigkeit bleibt bei 10.000.000 Iterationen fast gleich:
Benchmark 1: .NET 8 version (10000000 iterations)
Time (mean ± σ): 3.984 s ± 0.139 s [User: 2.946 s, System: 0.009 s]
Range (min … max): 3.790 s … 4.182 s 10 runs
Benchmark 2: Native AOT version (10000000 iterations)
Time (mean ± σ): 3.956 s ± 0.041 s [User: 2.848 s, System: 0.004 s]
Range (min … max): 3.888 s … 4.016 s 10 runs
Summary
Native AOT version ran 1.01 ± 0.04 times faster than .NET 8 version
PDF zu PNG
Bei einer einzelnen Iteration der Konvertierung von Banner Edulink One.pdf in PNG läuft die AOT-Version etwa 1,88-mal schneller als die .NET 8-Version:
Benchmark 1: .NET 8 version (1 iteration)
Time (mean ± σ): 2.417 s ± 0.104 s [User: 1.334 s, System: 0.116 s]
Range (min … max): 2.295 s … 2.629 s 10 runs
Benchmark 2: Native AOT version (1 iteration)
Time (mean ± σ): 1.288 s ± 0.011 s [User: 0.573 s, System: 0.123 s]
Range (min … max): 1.274 s … 1.310 s 10 runs
Bei 20 Iterationen ist der Geschwindigkeitsunterschied vernachlässigbar:
Benchmark 1: .NET 8 version (20 iterations)
Time (mean ± σ): 25.048 s ± 0.223 s [User: 13.278 s, System: 2.312 s]
Range (min … max): 24.751 s … 25.423 s 10 runs
Benchmark 2: Native AOT version (20 iterations)
Time (mean ± σ): 25.213 s ± 0.114 s [User: 12.661 s, System: 2.275 s]
Range (min … max): 25.042 s … 25.350 s 10 runs
Summary
.NET 8 version ran 1.01 ± 0.01 times faster than Native AOT version
Für 3BigPreview.pdf ist die Native AOT-Version sogar mit 100 Iterationen schneller:
Benchmark 1: .NET 8 version (100 iterations)
Time (mean ± σ): 10.009 s ± 0.152 s [User: 5.298 s, System: 0.567 s]
Range (min … max): 9.677 s … 10.189 s 10 runs
Benchmark 2: Native AOT version (100 iterations)
Time (mean ± σ): 8.336 s ± 0.070 s [User: 3.405 s, System: 0.505 s]
Range (min … max): 8.247 s … 8.459 s 10 runs
Summary
Native AOT version ran 1.20 ± 0.02 times faster than .NET 8 version
Abschluss
Native AOT-Anwendungen starten im Vergleich zu regulärem .NET schneller. Die offiziellen Benchmarks zeigen auch, dass AOT-Anwendungen weniger Speicherbedarf haben.
Aber nach dem Start zeigen verwaltete Anwendungen normalerweise eine bessere Geschwindigkeit. Das liegt daran, dass JIT Zugriff auf Laufzeitinformationen hat. In Anwendungen mit langer Laufzeit kann es effektiveren Code basierend auf dynamischer profilgesteuerter Optimierung und anderen Techniken neu generieren.
Mit ASP.NET-Benchmarks können Sie verschiedene Konfigurationen aus Leistungssicht vergleichen. Die Ergebnisse hängen jedoch vom Betriebssystem und der Prozessorarchitektur ab. Sie müssen in Ihrer Zielumgebung eigene Benchmarks ausführen, um die optimale Bereitstellungskonfiguration zu finden.