Benchmarking con BenchmarkDotNet
Escriba microbenchmarks precisos con BenchmarkDotNet, analice el rendimiento y las asignaciones, y evite errores habituales.
Benchmarking con BenchmarkDotNet es una lección gratuita de C# Academy en CoddyKit. Esta es la lección 4 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de C# Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de C# Academy incluye 4 lecciones en total.
¿Por qué BenchmarkDotNet?
Realizar micropruebas de rendimiento es sorprendentemente difícil: el calentamiento del JIT, las pausas del GC, las cachés de la CPU y la planificación del sistema operativo introducen ruido. BenchmarkDotNet gestiona todo esto automáticamente y produce resultados estadísticamente fiables.
Instalación y primera prueba de rendimiento
Añada el paquete NuGet, cree una clase con métodos [Benchmark] y ejecute el código con BenchmarkRunner.Run. Ejecute siempre las pruebas de rendimiento en modo Release.
// dotnet add package BenchmarkDotNet
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
[MemoryDiagnoser] // report allocations
[RankColumn] // rank methods by speed
public class StringBenchmarks
{
[Benchmark(Baseline = true)]
public string Concatenation()
{
var s = "";
for (int i = 0; i < 100; i++) s += i;
return s;
}
[Benchmark]
public string StringBuilder()
{
var sb = new System.Text.StringBuilder();
for (int i = 0; i < 100; i++) sb.Append(i);
return sb.ToString();
}
}
// Program.cs (must run as Release):
BenchmarkRunner.Run<StringBenchmarks>();Lectura de la tabla de resultados
BenchmarkDotNet imprime una tabla de resultados con el tiempo medio, el error, la desviación estándar, la proporción y la asignación de memoria. A continuación se explica cómo interpretar cada columna.
// Example output:
// | Method | Mean | Error | StdDev | Ratio | Alloc |
// |-------------- |---------:|--------:|--------:|------:|------:|
// | Concatenation | 4,210 ns | 81 ns | 76 ns | 1.00 | 6.4KB |
// | StringBuilder | 312 ns | 4 ns | 4 ns | 0.07 | 1.6KB |
// Mean: average time per operation
// Error: half of 99.9% confidence interval
// StdDev: standard deviation — high value = noisy environment
// Ratio: relative to Baseline=true method (1.00)
// Alloc: managed heap allocated per operation
// Rule of thumb:
// Error should be < 5% of Mean for reliable results
// Run on an isolated machine (no background tasks)Pruebas de rendimiento parametrizadas
Utilice [Params] para ejecutar una prueba de rendimiento con varios tamaños de entrada. BenchmarkDotNet ejecuta todas las combinaciones y genera una fila independiente para cada valor del parámetro.
[MemoryDiagnoser]
public class SearchBenchmarks
{
[Params(10, 100, 1000, 10000)]
public int N;
private int[] _data = Array.Empty<int>();
[GlobalSetup]
public void Setup()
=> _data = Enumerable.Range(0, N).ToArray();
[Benchmark]
public bool LinearSearch()
=> Array.IndexOf(_data, N - 1) >= 0;
[Benchmark]
public bool BinarySearch()
=> Array.BinarySearch(_data, N - 1) >= 0;
}Configuración y limpieza
Utilice atributos del ciclo de vida para ejecutar una configuración única o una configuración y limpieza en cada iteración. De este modo, el coste de configuración queda fuera del tiempo medido de la prueba de rendimiento.
[MemoryDiagnoser]
public class DbQueryBenchmarks
{
private NpgsqlConnection _conn = null!;
private NpgsqlCommand _cmd = null!;
[GlobalSetup] // runs once before all benchmarks
public void Setup()
{
_conn = new NpgsqlConnection("...");
_conn.Open();
_cmd = new NpgsqlCommand("SELECT id FROM orders LIMIT 100", _conn);
}
[IterationSetup] // runs before EACH benchmark iteration
public void IterationSetup() { /* per-iteration prep */ }
[Benchmark]
public int QueryOrders() => (int)_cmd.ExecuteScalar()!;
[GlobalCleanup] // runs once after all benchmarks
public void Cleanup() { _cmd.Dispose(); _conn.Dispose(); }
}Diagnóstico de memoria
[MemoryDiagnoser] informa de las asignaciones en el montón administrado por operación. Combínelo con las opciones de [GcForce] para controlar el comportamiento del GC durante las pruebas de rendimiento.
[MemoryDiagnoser] // adds Alloc column to results
public class AllocationBenchmarks
{
[Benchmark]
public int[] AllocateArray() => new int[1000];
[Benchmark]
public Span<int> UseStackalloc()
{
Span<int> span = stackalloc int[1000];
return span; // WARN: for illustration; don't return stackalloc
}
[Benchmark]
public int[] ArrayPoolRent()
{
var arr = System.Buffers.ArrayPool<int>.Shared.Rent(1000);
System.Buffers.ArrayPool<int>.Shared.Return(arr);
return arr;
}
}Diagnósticos: ETW y desensamblado
BenchmarkDotNet admite varios diagnósticos. DisassemblyDiagnoser muestra el código ensamblador nativo real generado, lo que resulta perfecto para verificar las optimizaciones del JIT.
using BenchmarkDotNet.Diagnosers;
[DisassemblyDiagnoser( // show generated ASM
maxDepth: 2,
printSource: true,
exportGithubMarkdown: true)]
[MemoryDiagnoser]
public class VectorizationBenchmarks
{
private float[] _data = new float[1024];
[Benchmark]
public float SumScalar()
{
float sum = 0;
foreach (var v in _data) sum += v;
return sum;
}
[Benchmark]
public float SumVectorized()
=> System.Numerics.Vector<float>.Count > 1
? VectorSum(_data)
: SumScalar();
}Configuraciones de trabajos
Los trabajos controlan el entorno de ejecución de la prueba de rendimiento: el número de iteraciones de calentamiento, las invocaciones por iteración, la versión del runtime de .NET y la configuración del JIT. Utilice trabajos predefinidos o personalizados.
using BenchmarkDotNet.Jobs;
// Compare JIT vs Native AOT performance:
[SimpleJob(RuntimeMoniker.Net90)] // .NET 9 JIT
[SimpleJob(RuntimeMoniker.NativeAot90)] // .NET 9 AOT
[MemoryDiagnoser]
public class RuntimeComparison
{
[Benchmark]
public string SerializeJson()
{
var obj = new { Name = "Alice", Age = 30 };
return System.Text.Json.JsonSerializer.Serialize(obj);
}
}
// Custom job for more warmup:
[Config(typeof(MyConfig))]
public class PreciseBenchmarks
{
private class MyConfig : ManualConfig
{
public MyConfig()
=> AddJob(Job.Default.WithWarmupCount(5).WithIterationCount(20));
}
}Exportación de resultados
BenchmarkDotNet exporta los resultados en varios formatos: Markdown (para solicitudes de incorporación de cambios en GitHub), CSV (para hojas de cálculo), JSON y gráficos HTML. Las opciones de exportación se configuran en la configuración del trabajo.
using BenchmarkDotNet.Exporters;
using BenchmarkDotNet.Exporters.Csv;
[Config(typeof(ExportConfig))]
public class MyBenchmarks { /* ... */ }
public class ExportConfig : ManualConfig
{
public ExportConfig()
{
AddExporter(MarkdownExporter.GitHub);
AddExporter(CsvExporter.Default);
AddExporter(HtmlExporter.Default);
// Results in: BenchmarkDotNet.Artifacts/results/
}
}
// CLI usage to run specific benchmarks:
// dotnet run -c Release -- --filter "*StringBenchmarks*"
// dotnet run -c Release -- --list all (list all benchmarks)Caso real: optimización antes y después
Uso de BenchmarkDotNet para validar una optimización de rendimiento mediante la comparación de enfoques ingenuos, con pools y basados en Span.
[MemoryDiagnoser]
[RankColumn]
public class ParsingBenchmarks
{
private const string Input = "2025-06-15T08:30:00Z";
[Benchmark(Baseline = true)]
public DateTime ParseViaString()
=> DateTime.Parse(Input);
[Benchmark]
public DateTime ParseExact()
=> DateTime.ParseExact(Input,
"yyyy-MM-ddTHH:mm:ssZ",
System.Globalization.CultureInfo.InvariantCulture);
[Benchmark]
public bool TryParse()
=> DateTime.TryParseExact(Input.AsSpan(),
"yyyy-MM-ddTHH:mm:ssZ",
System.Globalization.CultureInfo.InvariantCulture,
System.Globalization.DateTimeStyles.None,
out _);
}Comprobación rápida
¿Por qué deben ejecutarse las pruebas de rendimiento de BenchmarkDotNet con dotnet run -c Release?
Recapitulación: pruebas de rendimiento con BenchmarkDotNet
Conclusiones clave:
- Instale mediante NuGet, marque los métodos con
[Benchmark]y ejecute condotnet run -c Release [MemoryDiagnoser]: añade la columna de asignaciones;[RankColumn]: clasifica por velocidad[Params]: ejecuta la prueba con varios tamaños de entrada para determinar la complejidad algorítmica[GlobalSetup]/[GlobalCleanup]: configuración única fuera del tiempo medido[DisassemblyDiagnoser]: inspecciona el ASM nativo generado para verificar las optimizaciones del JIT- Utilice trabajos para comparar el JIT de .NET 9 con Native AOT y con distintas cantidades de iteraciones
Preguntas frecuentes
¿La lección «Benchmarking con BenchmarkDotNet» es gratis?
Sí — el texto completo de «Benchmarking con BenchmarkDotNet» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de C# Academy, actualiza a CoddyKit PRO. El curso de C# Academy incluye 4 lecciones en total.
¿Qué aprenderé en «Benchmarking con BenchmarkDotNet»?
Escriba microbenchmarks precisos con BenchmarkDotNet, analice el rendimiento y las asignaciones, y evite errores habituales. Practicas C# Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.
¿Necesito experiencia previa para empezar C# Academy?
No se requiere experiencia previa. C# Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 4 de 4.
¿Cuánto tiempo toma la lección «Benchmarking con BenchmarkDotNet»?
La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.
¿Puedo escribir y ejecutar código en esta lección de C# Academy?
Sí. Cada lección de C# Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.
Todas las lecciones de este curso
- Compilación Native AOT
- Trimming y limitaciones de la reflexión
- ReadyToRun y compilación por niveles
- Benchmarking con BenchmarkDotNet