Benchmark con BenchmarkDotNet
Scriva micro-benchmark accurati con BenchmarkDotNet, analizzi throughput e allocazioni ed eviti gli errori più comuni.
Benchmark con BenchmarkDotNet è una lezione C# Academy gratuita su CoddyKit. Questa è la lezione 4 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento C# Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso C# Academy include 4 lezioni in totale.
Perché BenchmarkDotNet?
Il micro-benchmarking è sorprendentemente difficile: il riscaldamento del JIT, le pause del GC, le cache della CPU e la pianificazione del sistema operativo introducono tutti del rumore. BenchmarkDotNet gestisce automaticamente tutti questi aspetti e produce risultati statisticamente affidabili.
Installazione e primo benchmark
Aggiunga il pacchetto NuGet, crei una classe con metodi [Benchmark] ed esegua il benchmark con BenchmarkRunner.Run. Esegua sempre i benchmark in modalità 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>();Lettura della tabella dei risultati
BenchmarkDotNet stampa una tabella dei risultati con tempo medio, errore, deviazione standard, rapporto e allocazione di memoria. Ecco come interpretare ogni colonna.
// 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)Benchmark parametrizzati
Usi [Params] per eseguire un benchmark con più dimensioni di input. BenchmarkDotNet esegue ogni combinazione e genera una riga separata per ogni valore del parametro.
[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;
}Configurazione e pulizia
Usi gli attributi del ciclo di vita per eseguire una configurazione una tantum oppure la configurazione e la pulizia a ogni iterazione. In questo modo il costo della configurazione non rientra nel tempo misurato del benchmark.
[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(); }
}Diagnostica della memoria
[MemoryDiagnoser] segnala le allocazioni nell'heap gestito per operazione. Lo abbini alle opzioni di [GcForce] per controllare il comportamento del GC durante i benchmark.
[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;
}
}Diagnostici: ETW e disassembly
BenchmarkDotNet supporta diversi strumenti diagnostici. DisassemblyDiagnoser mostra l'effettivo output dell'assembly nativo, perfetto per verificare le ottimizzazioni 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();
}Configurazioni dei job
I job controllano l'ambiente di esecuzione del benchmark: numero di iterazioni di riscaldamento, invocazioni per iterazione, versione del runtime .NET e impostazioni del JIT. Usi job predefiniti o personalizzati.
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));
}
}Esportazione dei risultati
BenchmarkDotNet esporta i risultati in diversi formati: Markdown (per le PR su GitHub), CSV (per i fogli di calcolo), JSON e grafici HTML. Le opzioni di esportazione sono configurate nella configurazione del job.
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 reale: ottimizzazione prima e dopo
Uso di BenchmarkDotNet per convalidare un'ottimizzazione delle prestazioni, confrontando approcci ingenui, basati su pool e basati su 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 _);
}Verifica rapida
Perché i benchmark di BenchmarkDotNet devono essere eseguiti con dotnet run -c Release?
Riepilogo: benchmarking con BenchmarkDotNet
Punti chiave:
- Installi tramite NuGet, contrassegni i metodi con
[Benchmark]ed esegua condotnet run -c Release [MemoryDiagnoser]: aggiunge la colonna delle allocazioni;[RankColumn]: classifica in base alla velocità[Params]: esegue il benchmark con più dimensioni di input per individuare la complessità algoritmica[GlobalSetup]/[GlobalCleanup]: configurazione una tantum al di fuori del tempo misurato[DisassemblyDiagnoser]: esamina l'ASM nativo generato per verificare le ottimizzazioni del JIT- Usi i job per confrontare il JIT di .NET 9, Native AOT e diversi numeri di iterazioni
Domande Frequenti
La lezione «Benchmark con BenchmarkDotNet» è gratuita?
Sì — il testo completo di «Benchmark con BenchmarkDotNet» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso C# Academy, passa a CoddyKit PRO. Il corso C# Academy include 4 lezioni in totale.
Cosa imparerò in «Benchmark con BenchmarkDotNet»?
Scriva micro-benchmark accurati con BenchmarkDotNet, analizzi throughput e allocazioni ed eviti gli errori più comuni. Eserciti C# Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.
Ho bisogno di esperienza per iniziare C# Academy?
Non è richiesta alcuna esperienza precedente. C# Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 4 di 4.
Quanto tempo richiede la lezione «Benchmark con BenchmarkDotNet»?
La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.
Posso scrivere ed eseguire codice in questa lezione C# Academy?
Sì. Ogni lezione C# Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.
Tutte le lezioni di questo corso
- Compilazione Native AOT
- Trimming e limitazioni della reflection
- ReadyToRun e compilazione a livelli
- Benchmark con BenchmarkDotNet