Esta página puede contener texto traducido automáticamente.
Rendimiento de C# Native AOT
¿Qué tan rápidas son las aplicaciones .NET Native AOT en comparación con el código administrado normal? ¿Puede AOT superar a JIT? ¿Cómo evaluar las aplicaciones Native AOT?
Este artículo es parte de una serie sobre Native AOT en .NET. Si no está familiarizado con Native AOT, lea primero la parte Cómo desarrollar aplicaciones Native AOT en .NET.
Este artículo compara el rendimiento de .NET y Native AOT. Primero, revisaremos los benchmarks oficiales de Microsoft. Permiten comparar diferentes opciones de implementación de .NET para aplicaciones ASP.NET simples.
Luego, aprenderá a ejecutar sus propios puntos de referencia utilizando BenchmarkDotNet y las herramientas hyperfine. Estos puntos de referencia le permiten medir la velocidad del código en su entorno.
Puntos de referencia de ASP.NET
El equipo de ASP.NET mantiene una infraestructura sólida para las pruebas de rendimiento. Prueban varios escenarios en diferentes entornos.
Estamos muy interesados en los puntos de referencia de Native AOT. La principal fuente de información es el siguiente panel de PowerBI. Los datos que se encuentran allí se basan en 3 "ballenas": aplicaciones de prueba, escenarios de implementación y métricas.
Aplicaciones de prueba
Puede encontrar el código fuente de los benchmarks y las aplicaciones de prueba en el repositorio aspnet/Benchmarks.
Los benchmarks de Native AOT comparan 3 tipos de aplicaciones:
- Stage1: una API mínima basada en HTTP y JSON. El código fuente de la aplicación se encuentra en
/src/BenchmarksApps/BasicMinimalApi
. - Stage1Grpc: una API similar basada en gRPC (
/src/BenchmarksApps/Grpc/BasicGrpc
) - Stage2: aplicación web completa que incluye base de datos y autenticación
(
/src/BenchmarksApps/TodosApi
)
Escenarios de implementación de .NET
Las aplicaciones de prueba se ejecutan en diferentes entornos. En este momento, los puntos de referencia utilizan máquinas virtuales Windows y Linux con 28 núcleos. También hay entornos Linux separados para procesadores ARM e Intel.
Las aplicaciones también se prueban en diferentes configuraciones. Una aplicación en alguna configuración define un "escenario".
Puede mantener presionada la tecla Ctrl (o ⌘) para seleccionar varios escenarios o entornos en el panel de PowerBI.
Métricas
Los puntos de referencia recopilan métricas fundamentales para cada aplicación implementada. Por ejemplo, las pruebas miden la cantidad de solicitudes por segundo (RPS), el tiempo de inicio y el conjunto de trabajo de memoria máxima.
Eso nos permite comparar los valores de las métricas para varias configuraciones de la misma aplicación.
Comparación de rendimiento
Compararemos los escenarios de StageX con StageXAot y StageXAotSpeedOpt. Utilizan la siguiente configuración:
Escenario | Argumentos de compilación de dotnet publish |
---|---|
StageX | PublishAot=false EnableRequestDelegateGenerator=false |
Stage2Aot | PublishAot=true StripSymbols=true |
Stage2AotSpeedOpt | PublishAot=true StripSymbols=true OptimizationPreference=Speed |
Todos los escenarios anteriores también utilizan la variable de entorno
DOTNET_GCDynamicAdaptationMode=1
.
Los escenarios StageXAotSpeedOpt permiten estimar el impacto de la configuración OptimizationPreference = Speed.
También puede revisar los escenarios de StageXTrimR2RSingleFile. Dichos escenarios corresponden a la implementación ReadyToRun recortada, que es otra forma de compilación anticipada en .NET. A veces, es una buena alternativa a Native AOT.
Estos son los resultados de la comparación de rendimiento actual para .NET 9 Release Candidate (septiembre de 2024):
Tiempo de inicio
Las aplicaciones AOT se inician mucho más rápido que las versiones administradas. Esto es así tanto para las aplicaciones de Stage1 como de Stage2 y para todos los entornos. Resultados de muestra:
Escenario | Tiempo de inicio (ms) |
---|---|
Stage2AotSpeedOpt | 100 |
Stage2Aot | 109 |
Stage2 | 528 |
Conjunto de trabajo
El conjunto de trabajo máximo para las aplicaciones Native AOT es menor que para las versiones administradas. En Linux, las versiones administradas usan aproximadamente entre 1,5 y 2 veces más RAM que las versiones AOT. Por ejemplo:
Escenario | Conjunto de trabajo máximo (MB) |
---|---|
Stage1Aot | 56 |
Stage1AotSpeedOpt | 57 |
Stage1 | 126 |
En Windows, la diferencia es menor. Especialmente, para Stage2:
Escenario | Conjunto de trabajo máximo (MB) |
---|---|
Stage2Aot | 152 |
Stage2AotSpeedOpt | 150 |
Stage2 | 167 |
Solicitudes por segundo
Los valores de RPS más altos significan una aplicación más rápida. La aplicación ligera Stage1 generalmente maneja alrededor de 800-900K solicitudes por segundo. La aplicación Stage2 más grande solo maneja alrededor de 200K solicitudes.
Para la aplicación Stage2, la versión .NET maneja más solicitudes que las versiones AOT en todos los entornos. La velocidad de la versión Stage2AotSpeedOpt a veces es similar, pero, por lo general, se encuentra entre Stage2 y Stage2Aot. Estos son los resultados típicos:
Escenario | RPS |
---|---|
Stage2 | 235.008 |
Stage2AotSpeedOpt | 215.637 |
Stage2Aot | 194.264 |
Los resultados de la aplicación Stage1 son similares en Intel Linux e Intel Windows. Sin embargo, en Ampere Linux, AOT supera a la versión administrada. Resultados de muestra de Ampere Linux:
Escenario | RPS |
---|---|
Stage1AotSpeedOpt | 929.524 |
Stage1Aot | 912.344 |
Stage1 | 844.659 |
Por lo tanto, el entorno y el código de la aplicación pueden afectar significativamente la velocidad. Tiene sentido ejecutar sus propios puntos de referencia para estimar los beneficios de Native AOT para su proyecto. Escribamos puntos de referencia personalizados sin la infraestructura de prueba de Microsoft.
Evaluación comparativa de aplicaciones Native AOT
Usaremos 2 tipos de benchmarks. El primero se basa en BenchmarkDotNet, la biblioteca popular para realizar benchmarks de código .NET. Estos benchmarks comparan la velocidad pura, excluyendo el tiempo de inicio.
El segundo se basa en la herramienta hyperfine. Permite comparar el tiempo de ejecución de dos comandos de shell. Estos puntos de referencia comparan la velocidad general, incluido el tiempo de inicio.
No compararemos el consumo de memoria aquí. En este momento, el diagnóstico NativeMemoryProfiler
en BenchmarkDotNet no es compatible con el entorno de ejecución de Native AOT. Hyperfine tampoco
realiza un seguimiento del uso de la memoria actualmente.
Puede descargar el código fuente del repositorio NativeAotBenchmarks en GitHub. Le recomendamos que los pruebe en su entorno. Este artículo describe los resultados de una computadora portátil con Windows 11 con procesador Intel Core i9-13900H y 16 Gb de RAM.
Asegúrese de ejecutar los benchmarks correctamente. Estas son las recomendaciones habituales:
- Use la compilación Release.
- Desactive todas las aplicaciones excepto el proceso de evaluación comparativa. Por ejemplo, deshabilite el software antivirus, cierre Visual Studio y un navegador web.
- Mantenga su computadora portátil enchufada y use el mejor modo de rendimiento.
- Utilice los mismos datos de entrada en los escenarios que se comparan.
Casos de prueba
Realizaremos pruebas comparativas en 2 escenarios en .NET 8:
1. Código C# simple para una compresión de cadena usando el conteo de caracteres repetidos. Por ejemplo, la cadena "aabcccccaaa" se convertiría en "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. Una tarea más pesada conversión de PDF a PNG que utiliza Docotic.Pdf.
Requisitos previos
Instalar prerrequisitos para la implementación de .NET Native AOT.
Instalar hyperfine para ejecutar los benchmarks correspondientes.
Para realizar pruebas comparativas de PDF a PNG, obtenga una clave de licencia gratuita por tiempo
limitado en la página Descargar la biblioteca PDF de C# .NET. Debe aplicar la clave de licencia en Helper.cs
.
BenchmarkDotNet
Estos puntos de referencia se encuentran en el proyecto NativeAotBenchmarks
. Comparamos los
resultados de RuntimeMoniker.NativeAot80 y RuntimeMoniker.Net80. De forma predeterminada,
BenchmarkDotNet crea código Native AOT con la configuración OptimizationPreference=Speed
.
BenchmarkDotNet realiza 6 o más iteraciones de calentamiento. Esto ayuda a JIT a precompilar el código y recopilar algunas estadísticas. Por lo tanto, estos puntos de referencia excluyen el tiempo de inicio de la comparación.
Compresión de cadenas
El benchmark CompressString
para la compresión de cadenas utiliza una cadena larga con caracteres
duplicados. El error común sería generar una cadena aleatoria. En tal caso, los benchmarks para
Native AOT y .NET 8 utilizarían cadenas de entrada diferentes. Es posible utilizar cadenas
aleatorias, pero es necesario inicializar un generador aleatorio con la misma semilla.
La versión Native AOT se ejecuta aproximadamente 1,08 veces más rápido que la versión .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 a PNG
Los benchmarks de PDF a PNG procesan documentos PDF en la memoria, lo que permite excluir la interacción con el sistema de archivos. Las operaciones de E/S con un disco pueden distorsionar los resultados del benchmark.
Probamos la velocidad con dos documentos PDF. El primero, Banner Edulink One.pdf, es más complejo. Se convierte a un PNG de 72 ppp y requiere más tiempo para procesarse. La versión .NET 8 es un poco más rápida para este documento:
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 |
El segundo documento es más pequeño y más simple. Se convierte a un PNG de 300 ppp. Y la velocidad es casi igual:
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
Estos puntos de referencia se encuentran en el proyecto NativeAotTestApp
. El proyecto no utiliza
la configuración OptimizationPreference=Speed
. Puede habilitarla en NativeAotTestApp.csproj:
<OptimizationPreference>Speed</OptimizationPreference>
Utilice el script benchmark.bat para ejecutar pruebas en Windows. Puede convertirlo a Bash para
sistemas operativos basados en Unix/Linux. El script crea versiones .NET 8 y Native AOT de la
misma aplicación. Luego, compara su rendimiento con comandos similares:
hyperfine --warmup 3 "net8-app.exe" "native-aot-app.exe"
El calentamiento en modo hiperfino ayuda a iniciar aplicaciones de prueba en cachés de disco "calientes". A diferencia de BenchmarkDotNet, el calentamiento hiperfino no ayuda a JIT. Por lo tanto, los puntos de referencia hiperfinos comparan la velocidad total de la aplicación, incluido el tiempo de inicio.
Nuestra aplicación de prueba admite el argumento de recuento de iteraciones. Permite repetir el mismo código varias veces en un bucle simple:
for (int i = 0; i < iterationCount; ++i)
CompressString(args);
La idea es reducir el impacto de la diferencia de tiempo de inicio. Repetir el mismo código le da a JIT la oportunidad de recopilar más estadísticas de tiempo de ejecución y generar código más rápido.
Una situación común es la siguiente. La primera vez, ejecutas pruebas comparativas con una sola iteración. Una versión Native AOT funciona mucho más rápido. Luego, ejecutas las mismas pruebas comparativas con múltiples iteraciones y la velocidad total de ambas versiones se vuelve igual. Esto significa que después del inicio, una versión administrada es realmente más rápida.
Compresión de cadenas
Para 100.000 iteraciones de la misma compresión de cadena de entrada, el rendimiento de Native AOT es mejor:
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
Pero la velocidad se vuelve casi la misma para 10.000.000 de iteraciones:
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 a PNG
En una única iteración de conversión de Banner Edulink One.pdf a PNG, la versión AOT se ejecuta aproximadamente 1,88 veces más rápido que la versión .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
Para 20 iteraciones, la diferencia de velocidad es insignificante:
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
Para 3BigPreview.pdf, la versión Native AOT es más rápida incluso con 100 iteraciones:
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
Conclusión
Las aplicaciones Native AOT se inician más rápido que las .NET normales. Los puntos de referencia oficiales también muestran que las aplicaciones AOT ocupan menos memoria.
Sin embargo, después del inicio, las aplicaciones administradas suelen mostrar una mejor velocidad. Esto sucede porque JIT tiene acceso a la información de tiempo de ejecución. En aplicaciones de larga ejecución, puede regenerar un código más eficaz en función de la optimización dinámica guiada por perfiles y otras técnicas.
Los benchmarks de ASP.NET le permiten comparar distintas configuraciones desde la perspectiva del rendimiento. Sin embargo, los resultados dependen del sistema operativo y de la arquitectura del procesador. Debe ejecutar sus propios benchmarks en su entorno de destino para encontrar la configuración de implementación óptima.