Cette page peut contenir du texte traduit automatiquement.
Performances Native AOT en C#
À quelle vitesse les applications .NET Native AOT se comparent-elles au code managé standard ? Native AOT peut-il surpasser JIT ? Comment mesurer les performances des applications Native AOT ?

Cet article fait partie d'une série sur Native AOT dans .NET. Si vous ne connaissez pas Native AOT, lisez d'abord la partie Comment développer des applications Native AOT dans .NET.
Cet article compare les performances de .NET et de Native AOT. D'abord, nous examinerons les benchmarks officiels de Microsoft. Ils permettent de comparer différentes options de déploiement .NET pour des applications ASP.NET simples.
Ensuite, vous apprendrez à exécuter vos propres benchmarks à l'aide de BenchmarkDotNet et des outils hyperfine. De tels benchmarks permettent de mesurer la vitesse du code dans votre environnement.
Benchmarks ASP.NET
L'équipe ASP.NET maintient une infrastructure solide pour les tests de performance. Elle teste divers scénarios dans différents environnements.
Les benchmarks Native AOT sont ceux qui nous intéressent le plus. La source principale d'informations est le tableau de bord PowerBI suivant. Les données qui s'y trouvent reposent sur 3 « piliers » : les applications de test, les scénarios de déploiement et les métriques.
Applications de test
Vous pouvez trouver le code source des benchmarks et des applications de test dans le dépôt aspnet/Benchmarks.
Les benchmarks Native AOT comparent 3 types d'applications :
- Stage1 - une API minimale basée sur HTTP et JSON. Le code source de l'application se trouve dans
/src/BenchmarksApps/BasicMinimalApi. - Stage1Grpc - une API similaire basée sur gRPC (
/src/BenchmarksApps/Grpc/BasicGrpc) - Stage2 - une application web complète impliquant une base de données, l'authentification (
/src/BenchmarksApps/TodosApi)
Scénarios de déploiement .NET
Les applications de test sont exécutées dans différents environnements. À l'heure actuelle, les benchmarks utilisent des machines virtuelles Windows et Linux avec 28 cœurs. Il existe aussi des environnements Linux distincts pour les processeurs ARM et Intel.
Les applications sont également testées dans différentes configurations. Une application dans une certaine configuration définit un « scénario ».
Vous pouvez maintenir la touche Ctrl (ou ⌘) enfoncée pour sélectionner plusieurs scénarios ou environnements sur le tableau de bord PowerBI.
Métriques
Les benchmarks collectent des métriques fondamentales pour chaque application déployée. Par exemple, les tests mesurent le nombre de requêtes par seconde (RPS), le temps de démarrage et le working set maximal.
Cela nous permet de comparer les valeurs des métriques pour diverses configurations d'une même application.
Comparaison des performances
Nous comparerons les scénarios StageX avec StageXAot et StageXAotSpeedOpt. Ils utilisent la configuration suivante :
| Scenario | dotnet publish build arguments |
|---|---|
| StageX | PublishAot=false EnableRequestDelegateGenerator=false |
| Stage2Aot | PublishAot=true StripSymbols=true |
| Stage2AotSpeedOpt | PublishAot=true StripSymbols=true OptimizationPreference=Speed |
Tous les scénarios ci-dessus utilisent également la variable d'environnement DOTNET_GCDynamicAdaptationMode=1.
Les scénarios StageXAotSpeedOpt permettent d'estimer l'impact du paramètre OptimizationPreference = Speed.
Vous pouvez aussi examiner les scénarios StageXTrimR2RSingleFile. De tels scénarios correspondent à un déploiement ReadyToRun avec trimming, qui est une autre forme de compilation anticipée dans .NET. Parfois, c'est une bonne alternative à Native AOT.
Voici les résultats actuels de comparaison des performances pour .NET 9 Release Candidate (septembre 2024) :
Temps de démarrage
Les applications AOT démarrent beaucoup plus vite que les versions managées. C'est vrai pour les applications Stage1 et Stage2, et pour tous les environnements. Exemples de résultats :
| Scenario | Startup time (ms) |
|---|---|
| Stage2AotSpeedOpt | 100 |
| Stage2Aot | 109 |
| Stage2 | 528 |
Working set
Le working set maximal des applications Native AOT est inférieur à celui des versions managées. Sous Linux, les versions managées utilisent environ 1,5 à 2 fois plus de RAM que les versions AOT. Par exemple :
| Scenario | Max working set (MB) |
|---|---|
| Stage1Aot | 56 |
| Stage1AotSpeedOpt | 57 |
| Stage1 | 126 |
Sous Windows, l'écart est plus faible. En particulier pour Stage2 :
| Scenario | Max working set (MB) |
|---|---|
| Stage2Aot | 152 |
| Stage2AotSpeedOpt | 150 |
| Stage2 | 167 |
Requêtes par seconde
Des valeurs RPS plus élevées signifient une application plus rapide. L'application légère Stage1 gère généralement environ 800 à 900 K requêtes par seconde. L'application plus lourde Stage2 ne gère qu'environ 200 K requêtes.
Pour l'application Stage2, la version .NET traite plus de requêtes que les versions AOT dans tous les environnements. La vitesse de la version Stage2AotSpeedOpt est parfois proche. Mais, en général, elle se situe entre Stage2 et Stage2Aot. Voici les résultats typiques :
| Scenario | RPS |
|---|---|
| Stage2 | 235,008 |
| Stage2AotSpeedOpt | 215,637 |
| Stage2Aot | 194,264 |
Les résultats pour l'application Stage1 sont similaires sous Intel Linux et Intel Windows. Cependant, sous Ampere Linux, AOT bat la version managée. Exemples de résultats sous Ampere Linux :
| Scenario | RPS |
|---|---|
| Stage1AotSpeedOpt | 929,524 |
| Stage1Aot | 912,344 |
| Stage1 | 844,659 |
Ainsi, l'environnement et le code de l'application peuvent affecter significativement la vitesse. Il est pertinent d'exécuter vos propres benchmarks pour estimer les bénéfices de Native AOT pour votre projet. Écrivons des benchmarks personnalisés sans l'infrastructure de test Microsoft.
Mesure des performances des applications Native AOT
Nous utiliserons 2 types de benchmarks. Le premier repose sur BenchmarkDotNet - la bibliothèque populaire pour mesurer les performances du code .NET. Ces benchmarks comparent la vitesse pure, hors temps de démarrage.
Le second repose sur l'outil hyperfine. Il permet de comparer le temps d'exécution de deux commandes shell. Ces benchmarks comparent la vitesse globale, y compris le temps de démarrage.
Nous ne comparerons pas la consommation mémoire ici. À l'heure actuelle, le diagnostiqueur NativeMemoryProfiler de BenchmarkDotNet ne prend pas en charge le runtime Native AOT. hyperfine ne suit pas non plus l'utilisation mémoire pour le moment.
Vous pouvez télécharger le code source depuis le dépôt NativeAotBenchmarks sur GitHub. Nous vous encourageons à les essayer dans votre environnement. Cet article décrit des résultats obtenus sur un ordinateur portable Windows 11 équipé d'un processeur Intel Core i9-13900H et de 16 Go de RAM.
Assurez-vous d'exécuter les benchmarks correctement. Voici les recommandations courantes :
- Utilisez la version Release.
- Fermez toutes les applications, sauf le processus de benchmark. Par exemple, désactivez l'antivirus, fermez Visual Studio et un navigateur web.
- Gardez votre ordinateur portable branché et utilisez le mode de performance maximale.
- Utilisez les mêmes données d'entrée dans les scénarios comparés.
Cas de test
Nous mesurerons 2 scénarios dans .NET 8 :
1. Un code C# simple de compression de chaîne utilisant les comptes de caractères répétés. Par exemple, la chaîne "aabcccccaaa" deviendrait "a2b1c5a3" :
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. Une tâche plus lourde de conversion PDF en PNG qui utilise Docotic.Pdf.
Prérequis
Installez les prérequis pour le déploiement .NET Native AOT.
Installez hyperfine pour exécuter les benchmarks correspondants.
Pour les benchmarks PDF vers PNG, obtenez une clé de licence gratuite à durée limitée sur la page Télécharger la bibliothèque PDF C# .NET. Vous devez appliquer la clé de licence dans Helper.cs.
BenchmarkDotNet
Ces benchmarks se trouvent dans le projet NativeAotBenchmarks. Nous comparons les résultats pour RuntimeMoniker.NativeAot80 et RuntimeMoniker.Net80. Par défaut, BenchmarkDotNet compile le code Native AOT avec le paramètre OptimizationPreference=Speed.
BenchmarkDotNet effectue 6 itérations d'échauffement ou plus. Cela aide JIT à précompiler le code et à collecter certaines statistiques. Ainsi, de tels benchmarks excluent le temps de démarrage de la comparaison.
Compression de chaîne
Le benchmark CompressString pour la compression de chaîne utilise une longue chaîne contenant des caractères dupliqués. L'erreur courante serait de générer une chaîne aléatoire. Dans un tel cas, les benchmarks pour Native AOT et .NET 8 utiliseraient des chaînes d'entrée différentes. Il est possible d'utiliser des chaînes aléatoires, mais vous devez initialiser un générateur aléatoire avec la même graine.
La version Native AOT s'exécute environ 1,08 fois plus vite que la version .NET 8 :
| 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 vers PNG
Les benchmarks PDF vers PNG traitent des documents PDF en mémoire. Cela permet d'exclure l'interaction avec le système de fichiers. Les opérations d'E/S avec un disque peuvent biaiser les résultats des benchmarks.
Nous testons la vitesse avec deux documents PDF. Le premier, Banner Edulink One.pdf, est plus complexe. Il est converti en PNG 72 dpi et nécessite plus de temps de traitement. La version .NET 8 est légèrement plus rapide pour ce document :
| 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 |
Le deuxième document est plus petit et plus simple. Il est converti en PNG 300 dpi. Et la vitesse est presque identique :
| 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
Ces benchmarks se trouvent dans le projet NativeAotTestApp. Le projet n'utilise pas le paramètre OptimizationPreference=Speed. Vous pouvez l'activer dans NativeAotTestApp.csproj : <OptimizationPreference>Speed</OptimizationPreference>
Utilisez le script benchmark.bat pour exécuter les tests sous Windows. Vous pouvez le convertir en Bash pour les systèmes d'exploitation Unix/Linux. Le script génère les versions .NET 8 et Native AOT de la même application. Ensuite, il compare leurs performances avec des commandes similaires : hyperfine --warmup 3 "net8-app.exe" "native-aot-app.exe"
Les exécutions d'échauffement dans hyperfine aident à lancer les applications de test avec des caches disque « chauds ». Contrairement à BenchmarkDotNet, l'échauffement d'hyperfine n'aide pas JIT. Par conséquent, les benchmarks hyperfine comparent la vitesse totale de l'application, y compris le temps de démarrage.
Notre application de test prend en charge l'argument de nombre d'itérations. Cela permet de répéter le même code plusieurs fois dans une boucle simple :
for (int i = 0; i < iterationCount; ++i)
CompressString(args);
L'idée est de réduire l'impact de la différence de temps de démarrage. La répétition du même code donne à JIT la possibilité de collecter davantage de statistiques d'exécution et de générer un code plus rapide.
Une situation courante est la suivante. La première fois, vous exécutez les benchmarks avec une seule itération. Une version Native AOT fonctionne beaucoup plus vite. Puis, vous exécutez les mêmes benchmarks avec plusieurs itérations et la vitesse totale des deux versions devient identique. Cela signifie qu'après le démarrage, une version managée est en réalité plus rapide.
Compression de chaîne
Pour 100 000 itérations de la même compression de chaîne en entrée, les performances Native AOT sont meilleures :
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
Mais la vitesse devient presque identique pour 10 000 000 d'itérations :
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 vers PNG
Pour une seule itération de conversion de Banner Edulink One.pdf en PNG, la version AOT s'exécute environ 1,88 fois plus vite que la version .NET 8 :
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
Pour 20 itérations, la différence de vitesse est négligeable :
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
Pour 3BigPreview.pdf, la version Native AOT est plus rapide même avec 100 itérations :
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
Conclusion
Les applications Native AOT démarrent plus vite que le .NET classique. Les benchmarks officiels montrent aussi que les applications AOT ont une empreinte mémoire plus faible.
Mais après le démarrage, les applications managées affichent généralement de meilleures performances. Cela s'explique par le fait que JIT a accès aux informations d'exécution. Dans les applications de longue durée, il peut régénérer un code plus efficace à partir de l'optimisation dynamique guidée par le profil et d'autres techniques.
Les benchmarks ASP.NET vous permettent de comparer différentes configurations du point de vue des performances. Cependant, les résultats dépendent du système d'exploitation et de l'architecture du processeur. Vous devez exécuter vos propres benchmarks dans votre environnement cible pour trouver la configuration de déploiement optimale.