Performance Profiling & Benchmarking (BenchmarkDotNet)
Measure before you optimise. Use BenchmarkDotNet for precise benchmarks, and diagnostics tools for production profiling.
What You'll Learn
- • Benchmark with
BenchmarkDotNet— memory and speed analysis - • Compare collection types, string operations, and LINQ vs loops
- • Use
Stopwatch,Activity, andEventCountersfor profiling - • Read benchmark results and make data-driven decisions
🏎️ Real-World Analogy
BenchmarkDotNet is like a professional race track with timing equipment — controlled conditions, multiple laps, statistical analysis. Stopwatch is like timing yourself with a phone — quick but imprecise. EventCounters are like a car's dashboard gauges — real-time metrics in production. Never optimise based on gut feeling — always measure first.
BenchmarkDotNet — String Operations
Always run benchmarks in Release mode. BenchmarkDotNet handles warmup, multiple iterations, and statistical analysis — giving you reliable results with memory tracking.
String Concatenation Benchmark
Compare string concatenation, StringBuilder, and String.Join performance.
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Text;
// Run benchmarks: dotnet run -c Release
// NEVER benchmark in Debug mode — JIT optimizations are disabled
[MemoryDiagnoser] // Track memory allocations
[RankColumn] // Show ranking
public class StringBenchmarks
{
private readonly string[] _words = Enumerable
.Range(0, 1000)
.Select(i => $"word{i}")
.ToArray();
// ❌ String concatenation — O(n²) allocations
...Collection Performance Comparison
Choosing the right collection type can mean 1,000x performance difference. Benchmark lookups across List, HashSet, Dictionary, and Array. Compare LINQ, for loops, and Span for iteration.
Collection & Iteration Benchmarks
Benchmark lookup and summation across List, HashSet, Dictionary, Span, and LINQ.
using BenchmarkDotNet.Attributes;
[MemoryDiagnoser]
public class CollectionBenchmarks
{
private List<int> _list = null!;
private HashSet<int> _hashSet = null!;
private Dictionary<int, string> _dict = null!;
private int[] _array = null!;
[GlobalSetup]
public void Setup()
{
_array = Enumerable.Range(0, 10_000).ToArray();
_list = _array.ToList();
_hashSet = _array.ToHashSet();
_dict = _array.ToDictionary(x => x, x => $"item_{x}");
}
...Production Diagnostics
For production profiling, use Stopwatch for quick measurements, Activity for distributed tracing, and EventCounters for lightweight real-time metrics.
Stopwatch, Activity & EventCounters
Profile production code with timing, tracing, and custom metrics.
using System.Diagnostics;
// Stopwatch — quick ad-hoc timing
public class PerformanceHelper
{
// Simple timing wrapper
public static async Task<(T Result, TimeSpan Elapsed)> MeasureAsync<T>(
Func<Task<T>> action)
{
var sw = Stopwatch.StartNew();
var result = await action();
sw.Stop();
return (result, sw.Elapsed);
}
// Usage
public static async Task Demo()
{
var (users, elapsed) = await MeasureAsync(
async (
...Pro Tip
Use [MemoryDiagnoser] on every benchmark. Memory allocation (GC pressure) often matters more than raw speed. A method that's 2x faster but allocates 10x more memory may perform worse in production under load.
Common Mistakes
- • Benchmarking in Debug mode — JIT doesn't optimise, results are meaningless
- • Micro-optimising code that runs once — focus on hot paths and loops
- • Using
DateTime.Nowfor timing — it's not monotonic; useStopwatch
Lesson Complete!
You've mastered performance profiling and benchmarking. You're now ready for the Final Project — build an enterprise-grade C# application!
Sign up for free to track which lessons you've completed and get learning reminders.