0Pricing
C# Academy · Lektion

Benchmarking mit BenchmarkDotNet

Schreiben Sie präzise Mikrobenchmarks mit BenchmarkDotNet, analysieren Sie Durchsatz und Allokationen und vermeiden Sie häufige Fehler.

Benchmarking mit BenchmarkDotNet ist eine kostenlose C# Academy-Lektion auf CoddyKit. Dies ist Lektion 4 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des C# Academy-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der C# Academy-Kurs umfasst insgesamt 4 Lektionen.

Warum BenchmarkDotNet?

Microbenchmarking ist überraschend schwierig: JIT-Aufwärmphase, GC-Pausen, CPU-Caches und die Betriebssystemplanung führen allesamt zu Messrauschen. BenchmarkDotNet übernimmt all das automatisch und erzeugt statistisch zuverlässige Ergebnisse.

Installation und erster Benchmark

Fügen Sie das NuGet-Paket hinzu, erstellen Sie eine Klasse mit Methoden, die mit [Benchmark] gekennzeichnet sind, und führen Sie sie mit BenchmarkRunner.Run aus. Führen Sie Benchmarks immer im Release-Modus aus.

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

Ergebnistabelle lesen

BenchmarkDotNet gibt eine Ergebnistabelle mit Mittelwert, Fehler, Standardabweichung, Verhältnis und Speicherzuweisung aus. Im Folgenden erfahren Sie, wie die einzelnen Spalten zu interpretieren sind.

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

Parametrisierte Benchmarks

Verwenden Sie [Params], um einen Benchmark mit mehreren Eingabegrößen auszuführen. BenchmarkDotNet führt jede Kombination aus und erzeugt für jeden Parameterwert eine eigene Zeile.

[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 und Aufräumen

Verwenden Sie Lebenszyklusattribute, um einmalige oder für jede Iteration durchgeführte Setup- und Aufräumarbeiten auszuführen. So bleibt der Aufwand für die Vorbereitung aus der gemessenen Benchmarkzeit heraus.

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

Speicherdiagnose

[MemoryDiagnoser] meldet die Zuweisungen auf dem verwalteten Heap pro Vorgang. Kombinieren Sie es mit den Optionen von [GcForce], um das GC-Verhalten während der Benchmarks zu steuern.

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

Diagnoser: ETW und Disassembly

BenchmarkDotNet unterstützt mehrere Diagnoser. Der DisassemblyDiagnoser zeigt die tatsächliche Ausgabe des nativen Assemblers an und eignet sich ideal, um JIT-Optimierungen zu überprüfen.

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

Job-Konfigurationen

Jobs steuern die Ausführungsumgebung des Benchmarks – die Anzahl der Aufwärmiterationen, die Aufrufe pro Iteration, die .NET-Laufzeitversion und die JIT-Einstellungen. Verwenden Sie vordefinierte oder benutzerdefinierte Jobs.

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

Ergebnisse exportieren

BenchmarkDotNet exportiert Ergebnisse in mehreren Formaten – Markdown (für GitHub-PRs), CSV (für Tabellenkalkulationen), JSON und HTML-Diagramme. Die Exportoptionen werden in der Job-Konfiguration festgelegt.

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)

Praxisbeispiel: Optimierung vorher und nachher

Verwenden Sie BenchmarkDotNet, um eine Leistungsoptimierung zu überprüfen – durch den Vergleich von naiven, gepoolten und Span-basierten Ansätzen.

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

Kurzer Check

Warum müssen BenchmarkDotNet-Benchmarks mit dotnet run -c Release ausgeführt werden?

Zusammenfassung: Benchmarking mit BenchmarkDotNet

Wichtigste Erkenntnisse:

  • Installieren Sie BenchmarkDotNet über NuGet, kennzeichnen Sie Methoden mit [Benchmark] und führen Sie sie mit dotnet run -c Release aus
  • [MemoryDiagnoser]: fügt eine Spalte für Speicherzuweisungen hinzu; [RankColumn]: ordnet nach Geschwindigkeit
  • [Params]: führt Benchmarks mit mehreren Eingabegrößen aus, um die algorithmische Komplexität zu ermitteln
  • [GlobalSetup] / [GlobalCleanup]: einmaliges Setup außerhalb der gemessenen Zeit
  • [DisassemblyDiagnoser]: untersucht den erzeugten nativen ASM-Code, um JIT-Optimierungen zu überprüfen
  • Verwenden Sie Jobs, um .NET 9 JIT, Native AOT und unterschiedliche Iterationszahlen zu vergleichen

Häufig gestellte Fragen

Ist die Lektion „Benchmarking mit BenchmarkDotNet“ kostenlos?

Ja — der vollständige Text von „Benchmarking mit BenchmarkDotNet“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des C# Academy-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der C# Academy-Kurs umfasst insgesamt 4 Lektionen.

Was lerne ich in „Benchmarking mit BenchmarkDotNet“?

Schreiben Sie präzise Mikrobenchmarks mit BenchmarkDotNet, analysieren Sie Durchsatz und Allokationen und vermeiden Sie häufige Fehler. Du übst C# Academy mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.

Brauche ich Erfahrung, um C# Academy zu starten?

Keine Vorkenntnisse erforderlich. C# Academy auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 4 von 4.

Wie lange dauert die Lektion „Benchmarking mit BenchmarkDotNet“?

Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.

Kann ich in dieser C# Academy-Lektion Code schreiben und ausführen?

Ja. Jede C# Academy-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.

Alle Lektionen in diesem Kurs

  1. Native-AOT-Kompilierung
  2. Trimming und Einschränkungen durch Reflection
  3. ReadyToRun und tiered Compilation
  4. Benchmarking mit BenchmarkDotNet
← Zurück zu C# Academy