Cette page peut contenir du texte traduit automatiquement.
Performances C# Native AOT
Quelle est la vitesse des applications .NET Native AOT par rapport au code managé classique ? AOT peut-il surpasser JIT ? Comment évaluer les applications Native AOT ?
Cet article fait partie d'une série sur Native AOT dans .NET. Si vous n'êtes pas familier avec Native AOT, lisez d'abord la partie Comment développer des applications Native AOT dans .NET.
Cet article compare les performances de .NET et Native AOT. Tout d'abord, nous allons passer en revue 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 tests de performance à l'aide de BenchmarkDotNet et des outils hyperfine. Ces tests de performance vous 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 performances. Ils testent divers scénarios dans différents environnements.
Nous sommes particulièrement intéressés par les benchmarks Native AOT. La principale source d'informations est le tableau de bord PowerBI suivant. Les données qui y sont présentées sont basées sur 3 « baleines » : 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 référentiel aspnet/Benchmarks.
Les tests de performance 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 - application Web complète impliquant une base de données, une authentification
(
/src/BenchmarksApps/TodosApi
)
Scénarios de déploiement .NET
Les applications de test sont exécutées dans différents environnements. Actuellement, les tests utilisent des machines virtuelles Windows et Linux avec 28 cœurs. Il existe également des environnements Linux distincts pour les processeurs ARM et Intel.
Les applications sont également testées dans différentes configurations. Une application dans une configuration donnée 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 tests de performance collectent des mesures 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, la mémoire de travail maximale.
Cela nous permet de comparer les valeurs métriques pour différentes configurations de la même application.
Comparaison des performances
Nous allons comparer les scénarios StageX avec StageXAot et StageXAotSpeedOpt. Ils utilisent la configuration suivante :
Scénario | Arguments de construction dotnet publish |
---|---|
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 également consulter les scénarios StageXTrimR2RSingleFile. Ces scénarios correspondent à un déploiement ReadyToRun réduit, qui est une autre forme de compilation anticipée dans .NET. Parfois, c'est une bonne alternative à Native AOT.
Voici les résultats de comparaison des performances actuelles pour .NET 9 Release Candidate (septembre 2024) :
Temps de démarrage
Les applications AOT démarrent beaucoup plus rapidement que les versions gérées. Cela est vrai pour les applications Stage1 et Stage2 et pour tous les environnements. Exemples de résultats :
Scénario | Temps de démarrage (ms) |
---|---|
Stage2AotSpeedOpt | 100 |
Stage2Aot | 109 |
Stage2 | 528 |
Ensemble de travail
L'espace de travail maximal pour les applications Native AOT est inférieur à celui des versions gérées. Sous Linux, les versions gérées utilisent environ 1,5 à 2 fois plus de RAM que les versions AOT. Par exemple :
Scénario | Ensemble de travail max. (Mo) |
---|---|
Stage1Aot | 56 |
Stage1AotSpeedOpt | 57 |
Stage1 | 126 |
Sous Windows, la différence est plus faible. En particulier, pour Stage2 :
Scénario | Ensemble de travail max. (Mo) |
---|---|
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 000 requêtes par seconde. L'application Stage2, plus grande, ne gère qu'environ 200 000 requêtes.
Pour l'application Stage2, la version .NET gère plus de requêtes que les versions AOT dans tous les environnements. La vitesse de la version Stage2AotSpeedOpt est parfois proche. Mais, généralement, elle se situe entre Stage2 et Stage2Aot. Voici les résultats typiques :
Scénario | RPS |
---|---|
Stage2 | 235 008 |
Stage2AotSpeedOpt | 215 637 |
Stage2Aot | 194 264 |
Les résultats de l'application Stage1 sont similaires sur Intel Linux et Intel Windows. Cependant, sur Ampere Linux, AOT bat la version gérée. Exemples de résultats d'Ampere Linux :
Scénario | RPS |
---|---|
Stage1AotSpeedOpt | 929 524 |
Stage1Aot | 912 344 |
Stage1 | 844 659 |
Ainsi, l'environnement et le code de l'application peuvent affecter considérablement la vitesse. Il est logique d'exécuter vos propres tests pour estimer les avantages de Native AOT pour votre projet. Écrivons des tests personnalisés sans infrastructure de test Microsoft.
Analyse comparative des applications Native AOT
Nous utiliserons 2 types de benchmarks. Le premier est basé sur BenchmarkDotNet - la bibliothèque populaire pour le benchmarking du code .NET. Ces benchmarks comparent la vitesse pure, hors temps de démarrage.
Le second est basé sur l'outil hyperfine. Il permet de comparer le temps d'exécution de deux commandes shell. Ces tests comparent la vitesse globale, y compris le temps de démarrage.
Nous ne comparerons pas ici la consommation de mémoire. Pour le moment, le diagnostiqueur
NativeMemoryProfiler
de BenchmarkDotNet ne prend pas en charge l'exécution Native AOT.
hyperfine ne suit pas non plus l'utilisation de la mémoire pour le moment.
Vous pouvez télécharger le code source depuis le référentiel NativeAotBenchmarks sur GitHub. Nous vous encourageons à les essayer dans votre environnement. Cet article décrit les résultats d'un ordinateur portable Windows 11 avec un processeur Intel Core i9-13900H et 16 Go de RAM.
Assurez-vous d'exécuter correctement les tests de performance. Voici les recommandations courantes :
- Utilisez la version Release.
- Désactivez toutes les applications à l'exception du processus de test. Par exemple, désactivez le logiciel antivirus, fermez Visual Studio et un navigateur Web.
- Gardez votre ordinateur portable branché et utilisez le meilleur mode de performance.
- Utilisez les mêmes données d'entrée dans les scénarios comparés.
Cas de test
Nous allons comparer 2 scénarios dans .NET 8 :
1. Code C# simple pour une compression de chaîne utilisant le nombre 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 conversion PDF en PNG qui utilise Docotic.Pdf.
Prérequis
Installez prérequis pour le déploiement .NET Native AOT.
Installez hyperfine pour exécuter les tests de performance correspondants.
Pour les tests de conversion PDF en 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 le fichier Helper.cs
.
BenchmarkDotNet
Ces tests se trouvent dans le projet NativeAotBenchmarks
. Nous comparons les résultats de
RuntimeMoniker.NativeAot80 et RuntimeMoniker.Net80. Par défaut, BenchmarkDotNet génère du code
Native AOT avec le paramètre OptimizationPreference=Speed
.
BenchmarkDotNet effectue 6 itérations de préchauffage ou plus. Cela aide JIT à précompiler le code et à collecter certaines statistiques. Ainsi, ces benchmarks excluent le temps de démarrage de la comparaison.
Compression de chaîne
Le test de performance CompressString
pour la compression de chaîne utilise une longue chaîne avec
des caractères dupliqués. L'erreur courante serait de générer une chaîne aléatoire. Dans un tel cas,
les tests de performance 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 valeur de départ.
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 tests de performance PDF vers PNG traitent les 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 fausser les résultats des tests.
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 égale :
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 tests se trouvent dans le projet NativeAotTestApp
. Le projet n'utilise pas le paramètre
OptimizationPreference=Speed
. Vous pouvez l'activer dans le NativeAotTestApp.csproj :
<OptimizationPreference>Speed</OptimizationPreference>
Utilisez le script benchmark.bat pour exécuter des tests sous Windows. Vous pouvez le convertir en
Bash pour les systèmes d'exploitation basés sur Unix/Linux. Le script crée 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 de préchauffage dans hyperfine aident à démarrer les applications de test sur des caches de disque « chauds ». Contrairement à BenchmarkDotNet, le préchauffage hyperfine n'aide pas le 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. Il 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 au JIT des chances de collecter plus 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 des tests de performance avec une seule itération. Une version Native AOT fonctionne beaucoup plus rapidement. Ensuite, vous exécutez les mêmes tests de performance avec plusieurs itérations et la vitesse totale des deux versions devient égale. Cela signifie qu'après le démarrage, une version gérée est en fait plus rapide.
Compression de chaîne
Pour 100 000 itérations de la même compression de chaîne d'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 la même 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 rapidement que les applications .NET classiques. Les tests de performance officiels montrent également que les applications AOT ont une empreinte mémoire plus petite.
Mais après le démarrage, les applications gérées affichent généralement une meilleure vitesse. Cela se produit parce que JIT a accès aux informations d'exécution. Dans les applications à exécution longue, il peut régénérer un code plus efficace basé sur l'optimisation guidée par profil dynamique et d'autres techniques.
Les tests de performances ASP.NET vous permettent de comparer différentes configurations du point de vue des performances. Toutefois, les résultats dépendent du système d'exploitation et de l'architecture du processeur. Vous devez exécuter vos propres tests de performances dans votre environnement cible pour trouver la configuration de déploiement optimale.