プログラムのパフォーマンスを測定することは、最適化やボトルネックの特定において重要です。
BenchmarkDotNet は、C# で効率的かつ信頼性の高いベンチマークを実施できる強力なライブラリです。処理の実行時間の測定やメモリ使用量、GCの量を計測可能です。
本記事では、BenchmarkDotNet の基本から使い方までを分かりやすく解説し、具体的な使用例を交えて紹介します。この記事を参考にして、C# で開発したアプリケーションのパフォーマンスを改善するツールとして、BenchmarkDotNet を活用してみましょう。
オススメの参考書
C#の使い方が丁寧に解説しており、「基礎からしっかりと学びたい」という初心者の方にオススメの一冊です。サンプルコードも記載してあり、各章の最後に復習問題があるので理解度を確認しながら読み進めることができます。新しい C# のバージョンにも対応している書籍です。
BenchmarkDotNetとは?
BenchmarkDotNet は、.NET アプリケーションの性能を正確に測定するためのライブラリです。多くの手動による測定ツールと異なり、ガベージコレクションやJITコンパイルによる影響を最小限に抑えた正確なベンチマークを可能にします。
(–
このライブラリには以下のような特徴があります。
- 信頼性のあるベンチマークの結果を提供してくれる
- ベンチマークの結果をレポートしてくれる
- 公式サイトやコミュニティで多くの情報が共有されている
- 比較的簡単な設定でベンチマークができる
インストール手順
この記事では以下の環境でインストールをします。
開発環境
- OS:Windows 11
- IDE:Visual Studio 2022(Version 17.12.3)
- .NET SDK: .NET Framework 4.8
Visual Studio を起動し、新しいコンソールアプリケーション(.NET Framework)プロジェクトを作成します。
メニューバーから[プロジェクト] -> [NuGet パッケージの管理]の順に選択します。
NuGet パッケージマネージャー画面が開くので、左上の[参照]タブを選択して検索欄に「BenchmarkDotNet」を入力して検索します。検索一覧の中にある「BenchmarkDotNet」を選択して[インストール]ボタンをクリックします。
正常にインストールされていることを出力ウィンドウで確認します。
BenchmarkDotNetの使い方
まず、ベンチマークするメソッドを含むクラスを作成します。
ベンチマークするメソッドには[Benchmark]
属性を付けます。メソッドはpublic
である必要があります。また、引数の受け渡しはできないので注意が必要です。
// ベンチマーク用の専用クラス
[MemoryDiagnoser]
public class BenchmarkTest
{
[GlobalSetup]
public Setup()
{
// フィールド変数の初期化
// データの作成 等をここに記述する
}
// ベンチマークするメソッドに属性を追加する
[Benchmark]
public void Case1()
{
// ここに処理を記述する
}
// ベンチマークするメソッドに属性を追加する
[Benchmark]
public void Case2()
{
// ここに処理を記述する
}
}
- MemoryDiagnoser:結果にメモリ使用量の統計を出力するように指定します。
- Benchmark:計測したいメソッドに追加します。
ベンチマークを実行するには、エンドポイントに次の記述をします。
internal class Program
{
static void Main(string[] args)
{
BenchmarkRunner.Run<BenchmarkTest>();
}
}
あとは、Visual Studio から Release でビルドして実行する、もしくはプロジェクトフォルダから[Relese]フォルダ内の実行ファイル(*.exe)をコンソールから実行します。
コンソールに詳細なベンチマークの結果が出力されます。
BenchmarkDotNetの使用例
ここからは使用例を紹介します。
リストに格納されたランダムな数値を「for文のループで全て足した場合」と「LinqのSumメソッドで全て足した場合」のパフォーマンスを比較します。
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Running;
using System.Collections.Generic;
using System.Linq;
namespace Sample
{
internal class Program
{
static void Main(string[] args)
{
BenchmarkRunner.Run<BenchmarkTest>();
}
}
// ベンチマーク用の専用クラス
[MemoryDiagnoser]
public class BenchmarkTest
{
private List<int> numbers;
[GlobalSetup]
public void Setup()
{
numbers = Enumerable.Range(1, 10000).ToList();
}
[Benchmark]
public int SumWithForLoop()
{
int sum = 0;
for (int i = 0; i < numbers.Count; i++)
{
sum += numbers[i];
}
return sum;
}
[Benchmark]
public int SumWithLinq()
{
return numbers.Sum();
}
}
}
Release でビルドして実行します。メソッドの処理内容によりますが、今回の簡単な処理でも完了するまで少し時間がかかります。
実行が完了すると、2つのメソッドの統計情報が表示されます。
// * Detailed results *
BenchmarkTest.SumWithForLoop: DefaultJob
Runtime = .NET Framework 4.8.1 (4.8.9282.0), X86 LegacyJIT; GC = Concurrent Workstation
Mean = 11.166 us, StdErr = 0.033 us (0.29%), N = 14, StdDev = 0.122 us
Min = 10.954 us, Q1 = 11.102 us, Median = 11.185 us, Q3 = 11.252 us, Max = 11.316 us
IQR = 0.149 us, LowerFence = 10.879 us, UpperFence = 11.475 us
ConfidenceInterval = [11.028 us; 11.303 us] (CI 99.9%), Margin = 0.137 us (1.23% of Mean)
Skewness = -0.43, Kurtosis = 1.69, MValue = 2
BenchmarkTest.SumWithLinq: DefaultJob
Runtime = .NET Framework 4.8.1 (4.8.9282.0), X86 LegacyJIT; GC = Concurrent Workstation
Mean = 55.816 us, StdErr = 0.123 us (0.22%), N = 14, StdDev = 0.461 us
Min = 54.999 us, Q1 = 55.543 us, Median = 55.829 us, Q3 = 56.061 us, Max = 56.667 us
IQR = 0.517 us, LowerFence = 54.768 us, UpperFence = 56.837 us
ConfidenceInterval = [55.296 us; 56.337 us] (CI 99.9%), Margin = 0.520 us (0.93% of Mean)
Skewness = 0.17, Kurtosis = 2.25, MValue = 2
- Mean:平均実行時間(マイクロ秒単位)
- StdErr:平均実行時間の標準誤差
- N:試行回数
- StdDev:実行時間の標準偏差(データのばらつきの指標)
- Min, Max:実行時間の最小値と最大値
- Q1, Median, Q3:25%点、中央値、75%点(四分位数)
- IQR:四分位範囲(データ分布の広がり)
- LowerFence, UpperFence:異常値を判定する境界値
- ConfidenceInterval:平均値の信頼区間(精度の範囲)
- Margin:信頼区間の幅
- Skewness:データ分布の歪み具合
- Kurtosis:分布の尖り具合
- MValue: メトリックの比較値
これらの結果がサマリーとして表示されます。表示形式はマークダウンなので、そのまま貼り付けて使用できます。
| Method | Mean | Error | StdDev | Allocated |
|--------------- |---------:|---------:|---------:|----------:|
| SumWithForLoop | 11.17 us | 0.137 us | 0.122 us | - |
| SumWithLinq | 55.82 us | 0.520 us | 0.461 us | 24 B |
ここで注目したいのがMean
です。この列が処理の実行時間を表している部分になるので、SumWithForLoop
メソッドの方が処理時間が短いことが判ります。処理時間を改善したい場合はどちらのメソッドを使用すればいいか明確になるわけです。
このようにメソッド毎のパフォーマンスを簡単に比較することができます。
まとめ
この記事では、BenchmarkDotNet の基本から使い方までを解説しました。
BenchmarkDotNet は、簡単な記述で正確なパフォーマンス測定を可能にする優れたツールです。
この記事で紹介した方法を活用して、ボトルネックの特定やアプリケーションのパフォーマンスを向上してみてみましょう。
以上、最後まで読んで頂きありがとうございました。