콘텐츠로 이동

BenchmarkDotNet으로 C# 성능 측정

BenchmarkDotNet은 .NET 생태계의 표준 마이크로벤치마크 라이브러리입니다. 워밍업, 통계 분석, JIT 최적화 방지를 자동으로 처리하므로 Stopwatch보다 훨씬 신뢰할 수 있는 측정치를 제공합니다. 메모리 할당량까지 추적해 GC 최적화 효과를 정량화할 수 있습니다.


Terminal window
# NuGet 설치
dotnet add package BenchmarkDotNet
# Release 모드로 실행 (필수)
dotnet run -c Release
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
// 진입점
BenchmarkRunner.Run<StringBenchmarks>();

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

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

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

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 { }

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 최적화에 의한 측정값 왜곡을 방지하세요.