BenchmarkDotNet for .NET Performance: A Complete Guide
Learn how to use BenchmarkDotNet to measure and optimize .NET code performance.
20+ years shipping production .NET services in enterprise systems. Drawn from code that ran under real load.
- ✓Basic knowledge of C# and .NET
- ✓Familiarity with console applications
- ✓Understanding of performance concepts like JIT and GC
- BenchmarkDotNet is a powerful library for benchmarking .NET code.
- It runs benchmarks in isolation, multiple iterations, and warm-up to get accurate results.
- Use the [Benchmark] attribute on methods and run via console app.
- Avoid common pitfalls like dead code elimination and unobserved overhead.
- Integrate with CI to track performance regressions.
Imagine you're a chef trying to find the fastest way to chop vegetables. You could time yourself, but your speed varies based on mood, knife sharpness, and distractions. BenchmarkDotNet is like a robot chef that chops the same vegetable thousands of times, in a controlled environment, and gives you a precise average time. It eliminates noise so you can trust the results.
Performance is a critical aspect of software development, especially in high-throughput systems. However, measuring performance accurately is notoriously difficult. Common approaches like using Stopwatch in a loop are fraught with pitfalls: JIT compilation, garbage collection, and system noise can skew results. This is where BenchmarkDotNet comes in. It's a robust, open-source library that automates the benchmarking process, providing statistically sound measurements. In this tutorial, you'll learn how to set up BenchmarkDotNet, write meaningful benchmarks, avoid common mistakes, and apply it to real-world scenarios. We'll also explore a production incident where a lack of proper benchmarking led to a costly outage, and how to prevent similar issues. By the end, you'll be equipped to make data-driven performance decisions in your .NET projects.
Setting Up BenchmarkDotNet
To get started, add the BenchmarkDotNet NuGet package to your project. You can do this via the .NET CLI: dotnet add package BenchmarkDotNet. Then, create a console application. In your Program.cs, add the necessary using directives and configure the benchmark runner. Here's a minimal setup:
Writing Effective Benchmarks
A good benchmark isolates the code under test and avoids side effects. Use the [Benchmark] attribute on methods you want to measure. You can also use [Params] to test multiple inputs. For example, compare string concatenation methods:
Understanding BenchmarkDotNet Output
BenchmarkDotNet produces a detailed summary table with key metrics: Mean (average time), Error (margin of error), StdDev (standard deviation), and Allocated (memory per operation). It also provides hardware and runtime info. The output includes a summary of the benchmark environment and a results table. For example, the table above shows that StringBuilder is faster and allocates less memory for 100 concatenations. You can also export results to markdown, CSV, or HTML for reporting.
Avoiding Common Benchmarking Pitfalls
Common mistakes include not warming up the JIT, including setup code in the benchmark, and not preventing dead code elimination. BenchmarkDotNet handles warm-up and iteration automatically, but you must ensure your benchmark method returns a value or uses the result to prevent optimization. For example:
Advanced Benchmarking: Memory and Disassembly
BenchmarkDotNet can measure memory allocations and even disassemble your code to see the generated assembly. Use the [MemoryDiagnoser] attribute to include allocation data. For disassembly, add [DisassemblyDiagnoser]. This is useful for understanding low-level performance characteristics.
Integrating Benchmarks into CI/CD
To prevent performance regressions, run benchmarks as part of your CI pipeline. You can use BenchmarkDotNet's ability to export results and compare them against a baseline. For example, use the --exporters option to generate JSON or markdown reports. Then, use a tool like BenchmarkDotNet.Annotations to compare runs.
The Slow String Concatenation That Brought Down a Service
- Always benchmark before optimizing; intuition is often wrong.
- Use BenchmarkDotNet to compare alternatives in realistic scenarios.
- Small concatenations (fewer than 5 strings) are faster with + or interpolation.
- Profile in production-like conditions to avoid misleading results.
- Incorporate benchmarks into CI to catch regressions early.
dotnet run -c ReleaseCheck the summary table for mean time and memory allocations.| File | Command / Code | Purpose |
|---|---|---|
| Program.cs | using BenchmarkDotNet.Running; | Setting Up BenchmarkDotNet |
| StringBenchmark.cs | using BenchmarkDotNet.Attributes; | Writing Effective Benchmarks |
| Program.cs | using BenchmarkDotNet.Configs; | Understanding BenchmarkDotNet Output |
| PitfallBenchmark.cs | public class PitfallBenchmark | Avoiding Common Benchmarking Pitfalls |
| AdvancedBenchmark.cs | using BenchmarkDotNet.Attributes; | Advanced Benchmarking |
| ci-script.sh | dotnet run -c Release -- --exporters json --filter *MyBenchmark* | Integrating Benchmarks into CI/CD |
Key takeaways
Common mistakes to avoid
4 patternsRunning benchmarks in Debug mode
Not using the benchmark result
Including setup code in the benchmark method
Ignoring memory allocation results
Interview Questions on This Topic
Explain how BenchmarkDotNet ensures accurate benchmarking results.
Frequently Asked Questions
20+ years shipping production .NET services in enterprise systems. Drawn from code that ran under real load.
That's C# Advanced. Mark it forged?
3 min read · try the examples if you haven't