C# Academy · Lektion

Benchmarking med BenchmarkDotNet

Skriv korrekta mikrobenchmarktester med BenchmarkDotNet, analysera genomströmning och allokeringar och undvik vanliga fallgropar.

Lektion 4 av 412 steg

Benchmarking med BenchmarkDotNet är en gratis lektion i C# Academy på CoddyKit. Detta är lektion 4 av 4. Ni kan läsa hela lektionen gratis nedan och sedan öva praktiskt i webbläsaren med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt. Den ingår i lärvägen för C# Academy, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i C# Academy innehåller totalt 4 lektioner.

Varför BenchmarkDotNet?

Mikrobenchmarking är överraskande svårt: JIT-uppvärmning, GC-pauser, CPU-cache och schemaläggning i operativsystemet introducerar alla brus. BenchmarkDotNet hanterar allt detta automatiskt och producerar statistiskt tillförlitliga resultat.

Installation och första benchmarken

Lägg till NuGet-paketet, skapa en klass med metoderna [Benchmark] och kör med BenchmarkRunner.Run. Kör alltid benchmarkerna i Release-läge.

// 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>();

Läsa resultattabellen

BenchmarkDotNet skriver ut en resultattabell med medeltid, felmarginal, standardavvikelse, kvot och minnesallokering. Så här tolkar ni varje kolumn.

// 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)

Parameteriserade benchmarker

Använd [Params] för att köra en benchmark med flera indatastorlekar. BenchmarkDotNet kör varje kombination och genererar en separat rad för varje parametervärde.

[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;
}

Setup och cleanup

Använd livscykelattribut för att köra engångssetup eller setup och cleanup per iteration. På så sätt hålls kostnaden för setup utanför den uppmätta benchmarktiden.

[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(); }
}

Minnesdiagnostik

[MemoryDiagnoser] rapporterar allokeringar på den hanterade heapen per operation. Kombinera det med alternativen [GcForce] för att styra GC-beteendet under benchmarkerna.

[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;
    }
}

Diagnosverktyg: ETW och disassembly

BenchmarkDotNet stöder flera diagnosverktyg. DisassemblyDiagnoser visar den faktiska inbyggda assemblerutmatningen, vilket är perfekt för att verifiera JIT-optimeringar.

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();
}

Jobbkonfigurationer

Jobb styr körmiljön för benchmarkerna – antal uppvärmningsiterationer, anrop per iteration, .NET-körmiljöversion och JIT-inställningar. Använd fördefinierade eller anpassade jobb.

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));
    }
}

Exportera resultat

BenchmarkDotNet exporterar resultat i flera format – Markdown (för GitHub-PR:er), CSV (för kalkylblad), JSON och HTML-diagram. Exportalternativen konfigureras i jobbkonfigurationen.

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)

I praktiken: jämförelse före och efter optimering

Använd BenchmarkDotNet för att validera en prestandaoptimering – jämför naiva, poolade och Span-baserade metoder.

[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 _);
}

Snabbkontroll

Varför måste BenchmarkDotNet-benchmarken köras med dotnet run -c Release?

Sammanfattning: benchmarking med BenchmarkDotNet

Viktiga lärdomar:

  • Installera via NuGet, markera metoder med [Benchmark] och kör med dotnet run -c Release
  • [MemoryDiagnoser]: lägger till en allokeringskolumn; [RankColumn]: rangordnar efter hastighet
  • [Params]: kör med flera indatastorlekar för att hitta den algoritmiska komplexiteten
  • [GlobalSetup] / [GlobalCleanup]: engångssetup utanför den uppmätta tiden
  • [DisassemblyDiagnoser]: granska genererad inbyggd ASM-kod för att verifiera JIT-optimeringar
  • Använd jobb för att jämföra .NET 9 JIT med Native AOT och olika antal iterationer
Gratis att börja

Lär dig C# med en AI-lärare – gratis

Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.

Kurser
93
Lektioner
346

Vanliga frågor

Är lektionen ”Benchmarking med BenchmarkDotNet” gratis?

Ja – hela texten till ”Benchmarking med BenchmarkDotNet” kan läsas gratis här på webben. Om Ni vill öva interaktivt med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt och låsa upp resten av kursen i C# Academy, kan Ni uppgradera till CoddyKit PRO. Kursen i C# Academy innehåller totalt 4 lektioner.

Vad lär jag mig i ”Benchmarking med BenchmarkDotNet”?

Skriv korrekta mikrobenchmarktester med BenchmarkDotNet, analysera genomströmning och allokeringar och undvik vanliga fallgropar. Ni övar på C# Academy med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.

Behöver jag någon erfarenhet för att börja lära mig C# Academy?

Du behöver inga förkunskaper. Utbildningen i C# Academy på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 4 av 4.

Hur lång tid tar lektionen ”Benchmarking med BenchmarkDotNet”?

De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.

Kan jag skriva och köra kod i den här C# Academy-lektionen?

Ja. Varje C# Academy-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.

Alla lektioner i den här kursen

  1. Native AOT-kompilering
  2. Trimning och reflektionsbegränsningar
  3. ReadyToRun och nivåindelad kompilering
  4. Benchmarking med BenchmarkDotNet
← Tillbaka till C# Academy