BenchmarkDotNetによるベンチマーク
BenchmarkDotNetで正確なマイクロベンチマークを記述し、スループットとアロケーションを分析して、よくある問題を避けます。
「BenchmarkDotNetによるベンチマーク」はCoddyKit上の無料C# Academyレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはC# Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 C# Academyコースには全4レッスンが含まれています。
なぜBenchmarkDotNetを使うのか
マイクロベンチマークは意外に難しいものです。JITのウォームアップ、GCの一時停止、CPUキャッシュ、OSのスケジューリングがすべてノイズを生み出します。BenchmarkDotNetはこれらを自動的に処理し、統計的に信頼できる結果を生成します。
インストールと最初のベンチマーク
NuGetパッケージを追加し、[Benchmark]メソッドを持つクラスを作成して、BenchmarkRunner.Runで実行します。ベンチマークは必ずReleaseモードで実行してください。
// 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>();結果テーブルの読み方
BenchmarkDotNetは、平均時間、誤差、標準偏差、比率、メモリ割り当てを含む結果テーブルを出力します。ここでは各列の解釈方法を説明します。
// 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)パラメーター化されたベンチマーク
[Params]を使用すると、複数の入力サイズでベンチマークを実行できます。BenchmarkDotNetはすべての組み合わせを実行し、パラメーター値ごとに個別の行を生成します。
[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;
}セットアップとクリーンアップ
ライフサイクル属性を使用して、1回だけ実行するセットアップや、反復ごとのセットアップとクリーンアップを実行します。これにより、セットアップのコストを測定対象のベンチマーク時間から除外できます。
[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(); }
}メモリ診断
[MemoryDiagnoser]は、操作ごとのマネージヒープ割り当てを報告します。[GcForce]オプションと組み合わせて、ベンチマーク中のGCの動作を制御できます。
[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;
}
}診断ツール:ETWと逆アセンブリ
BenchmarkDotNetは複数の診断ツールをサポートしています。DisassemblyDiagnoserは実際に生成されたネイティブアセンブリを表示するため、JITの最適化を検証するのに最適です。
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();
}ジョブ構成
ジョブは、ウォームアップ反復回数、反復ごとの呼び出し回数、.NETランタイムのバージョン、JIT設定など、ベンチマークの実行環境を制御します。定義済みのジョブまたはカスタムジョブを使用できます。
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));
}
}結果のエクスポート
BenchmarkDotNetは、Markdown(GitHubのPR向け)、CSV(スプレッドシート向け)、JSON、HTMLチャートなど、複数の形式で結果をエクスポートします。エクスポートのオプションはジョブ構成で設定します。
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)実践例:最適化前と最適化後
BenchmarkDotNetを使用してパフォーマンス最適化を検証します。単純なアプローチ、プールを使用するアプローチ、Spanベースのアプローチを比較します。
[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 _);
}確認問題
BenchmarkDotNetのベンチマークは、なぜdotnet run -c Releaseで実行する必要があるのでしょうか。
まとめ:BenchmarkDotNetによるベンチマーク
主なポイント:
- NuGetでインストールし、メソッドに
[Benchmark]を付け、dotnet run -c Releaseで実行します [MemoryDiagnoser]:割り当て列を追加します。[RankColumn]:速度順にランク付けします[Params]:複数の入力サイズで実行し、アルゴリズムの計算量を確認します[GlobalSetup]/[GlobalCleanup]:測定時間の外で1回だけセットアップまたはクリーンアップを行います[DisassemblyDiagnoser]:生成されたネイティブASMを調べて、JITの最適化を検証します- ジョブを使用して、.NET 9 JITとNative AOT、または異なる反復回数を比較します
よくある質問
「BenchmarkDotNetによるベンチマーク」レッスンは無料ですか?
はい。「BenchmarkDotNetによるベンチマーク」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、C# Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 C# Academyコースには全4レッスンが含まれています。
「BenchmarkDotNetによるベンチマーク」で何を学びますか?
BenchmarkDotNetで正確なマイクロベンチマークを記述し、スループットとアロケーションを分析して、よくある問題を避けます。 ブラウザで直接実行するハンズオンコードでC# Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
C# Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのC# Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。
「BenchmarkDotNetによるベンチマーク」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このC# Academyレッスンでコードを書いて実行できますか?
はい。すべてのC# Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- Native AOTコンパイル
- トリミングとリフレクションの制限
- ReadyToRunと階層型コンパイル
- BenchmarkDotNetによるベンチマーク