BenchmarkDotNet으로 C# 성능 측정
BenchmarkDotNet은 .NET 생태계의 표준 마이크로벤치마크 라이브러리입니다. 워밍업, 통계 분석, JIT 최적화 방지를 자동으로 처리하므로 Stopwatch보다 훨씬 신뢰할 수 있는 측정치를 제공합니다. 메모리 할당량까지 추적해 GC 최적화 효과를 정량화할 수 있습니다.
1. 기본 설정
섹션 제목: “1. 기본 설정”# NuGet 설치dotnet add package BenchmarkDotNet
# Release 모드로 실행 (필수)dotnet run -c Releaseusing BenchmarkDotNet.Attributes;using BenchmarkDotNet.Running;
// 진입점BenchmarkRunner.Run<StringBenchmarks>();2. 기본 벤치마크
섹션 제목: “2. 기본 벤치마크”[MemoryDiagnoser] // 메모리 할당 추적[SimpleJob(RuntimeMoniker.Net80)]public class StringBenchmarks{ private const string Input = "Hello, BenchmarkDotNet!";
[Benchmark(Baseline = true)] public string StringConcat() { string result = ""; for (int i = 0; i < 100; i++) result += Input[i % Input.Length]; return result; }
[Benchmark] public string StringBuilder() { var sb = new System.Text.StringBuilder(); for (int i = 0; i < 100; i++) sb.Append(Input[i % Input.Length]); return sb.ToString(); }
[Benchmark] public string StringCreate() { return string.Create(100, Input, static (span, src) => { for (int i = 0; i < span.Length; i++) span[i] = src[i % src.Length]; }); }}3. 결과 해석
섹션 제목: “3. 결과 해석”| Method | Mean | Error | StdDev | Ratio | Gen0 | Allocated ||-------------- |----------:|---------:|---------:|------:|-------:|----------:|| StringConcat | 4,521 ns | 23.14 ns | 21.65 ns | 1.00 | 3.4180 | 5,712 B || StringBuilder | 312 ns | 2.81 ns | 2.63 ns | 0.07 | 0.1535 | 256 B || StringCreate | 198 ns | 1.44 ns | 1.28 ns | 0.04 | 0.0153 | 26 B |
- Mean: 평균 실행 시간 (ns/μs/ms)- Ratio: Baseline 대비 비율 (낮을수록 빠름)- Gen0: GC 0세대 수집 빈도 (1000회당)- Allocated: 단일 실행당 힙 할당 바이트4. Params — 매개변수 변화 테스트
섹션 제목: “4. Params — 매개변수 변화 테스트”[MemoryDiagnoser]public class CollectionBenchmarks{ [Params(10, 100, 1000, 10_000)] public int N;
private int[] _data = null!;
[GlobalSetup] public void Setup() => _data = Enumerable.Range(0, N).ToArray();
[Benchmark(Baseline = true)] public int LinqSum() => _data.Sum();
[Benchmark] public int LoopSum() { int sum = 0; foreach (int x in _data) sum += x; return sum; }
[Benchmark] public int SpanSum() { ReadOnlySpan<int> span = _data; int sum = 0; for (int i = 0; i < span.Length; i++) sum += span[i]; return sum; }}5. Job 설정 — 여러 런타임 비교
섹션 제목: “5. Job 설정 — 여러 런타임 비교”[SimpleJob(RuntimeMoniker.Net80)][SimpleJob(RuntimeMoniker.Net60)][SimpleJob(RuntimeMoniker.NativeAot80)] // NativeAOT[MemoryDiagnoser][HtmlExporter] // HTML 보고서[CsvExporter] // CSV 내보내기public class CrossRuntimeBenchmarks{ [Benchmark] public double MathSqrt() => Math.Sqrt(12345.6789);}6. 커스텀 설정
섹션 제목: “6. 커스텀 설정”public class Config : ManualConfig{ public Config() { AddJob(Job.Default .WithRuntime(CoreRuntime.Core80) .WithWarmupCount(3) .WithIterationCount(10) .WithInvocationCount(1024));
AddDiagnoser(MemoryDiagnoser.Default); AddDiagnoser(new DisassemblyDiagnoser( new DisassemblyDiagnoserConfig(maxDepth: 2))); AddExporter(MarkdownExporter.GitHub); }}
[Config(typeof(Config))]public class MyBenchmarks { }7. 흔한 실수 방지
섹션 제목: “7. 흔한 실수 방지”public class CorrectBenchmarks{ private List<int> _list = null!;
[GlobalSetup] public void Setup() => _list = Enumerable.Range(0, 1000).ToList();
// ✅ 결과를 반환해 데드 코드 제거 방지 [Benchmark] public int SumCorrect() => _list.Sum();
// ❌ 결과를 버리면 JIT이 코드를 제거할 수 있음 [Benchmark] public void SumWrong() { _list.Sum(); }
// ✅ 여러 결과: Consumer 사용 [Benchmark] public void MultipleOps() { var consumer = new BenchmarkDotNet.Engines.Consumer(); consumer.Consume(_list.Sum()); consumer.Consume(_list.Count); }}BenchmarkDotNet은 [MemoryDiagnoser]와 [Params]를 함께 사용할 때 가장 유용합니다. 결과에서 Allocated 열을 주목해 할당 제로화(zero-alloc) 최적화 효과를 검증하고, [Baseline = true]로 개선 비율을 명확히 표시하세요. 항상 Release 모드로 실행하고, 벤치마크 메서드는 반드시 결과를 반환해 JIT 최적화에 의한 측정값 왜곡을 방지하세요.