dotnet/BenchmarkDotNetは.NETコードのベンチマークライブラリで、デファクトスタンダードとして使われています。クラスやアセンブリ単位に実行したり、ベンチマークの試行回数調整やパラメーターセットアップも容易で非常に使いやすく、私自身もよく使っています。
さて、ライブラリを書いているとは主にコンソールアプリケーションからベンチマークを実行するのですが、サクッとコード断片をLINQPadで書くついでにベンチマークを取りたいことがあります。今回はLINQPadでBenchmarkDotNetを使う方法を紹介します。
LINQPadでBenchmarkDotNetを使う
LINQPadで実行するとResultsペインでベンチマーク結果が表示されます。この状態を目指しましょう。
LINQPadにBenchmarkDotNetを追加する
LINQPadにBenchmarkDotNetを追加するには、NuGetパッケージを追加します。F4 > Add NuGet
を選択し、BenchmarkDotNet
を検索して追加します。
ベンチーマークコードを書く
リストから特定のレンジを拾うのにパフォーマンスがいいのはどのような書き方かを調べるためのベンチマークを書いてみましょう。
- SkipAndTake:
Skip
とTake
を使って範囲を取得 - Take:
Take
の範囲指定を使って範囲を取得 - GetRangeMethod:
GetRange
メソッドを使って範囲を取得
ベンチマークコードはクラスで書いても、いきなりUserQueryに書いても構いません。おすすめはクラスです。
クラスで書いて実行する
まずは慣れ親しんだクラスで書いてみましょう。今回は.NET 8と9でベンチマーク結果が変わるかを調べるため、以下のようなコードを書いてみます。
BenchmarkDotNetの属性を使ってベンチマークを書いています。GlobalSetup
でベンチマークの前処理を行い、Benchmark
でベンチマーク対象のメソッドを指定しています。属性もよく見かけるパターンですね。
// これで実行 BenchmarkRunner.Run<GetRangeBenchmark>(); // ベンチマークコード [HideColumns(Column.Job, Column.RatioSD, Column.AllocRatio)] [ShortRunJob(RuntimeMoniker.Net80)] [ShortRunJob(RuntimeMoniker.Net90)] [MemoryDiagnoser] [ReturnValueValidator(failOnError: true)] [Orderer(BenchmarkDotNet.Order.SummaryOrderPolicy.SlowestToFastest)] public class GetRangeBenchmark { private List<string> _userIds; [GlobalSetup] public void Setup() { _userIds = Enumerable.Range(1, 1000).Select(i => $"User{i}").ToList(); } [Benchmark(Baseline = true)] public List<string> SkipAndTake() { return _userIds.Skip(200).Take(200).ToList(); } [Benchmark] public List<string> Take() { return _userIds.Take(200..400).ToList(); } [Benchmark] public List<string> GetRangeMethod() { return _userIds.GetRange(200, 200); } }
実行する際はリリースビルドで実行することをお忘れなく。Debugビルドで実行するとちゃんと怒ってくれます、便利。
リリースビルドとして実行するには、Shift + Alt + O
で最適化を有効にしてF5実行します。画面右下の/o+
をクリックしてもいいですね。
実行すると無事に実行結果が表示されます。
クラスを書かずに実行する
LINQPadはクラスを書かずにメソッドをいきなり書き始められます。この場合、クラスはUserQueryとして扱われます。 ただ、クラスを書かないと属性を設定できないのでベンチマーク構成ができず不便です。1
void Main() { BenchmarkRunner.Run<UserQuery>(); } private List<string> _userIds; [GlobalSetup] public void Setup() { _userIds = Enumerable.Range(1, 1000).Select(i => $"User{i}").ToList(); } [Benchmark(Baseline = true)] public List<string> SkipAndTake() { return _userIds.Skip(200).Take(200).ToList(); } [Benchmark] public List<string> Take() { return _userIds.Take(200..400).ToList(); } [Benchmark] public List<string> GetRangeMethod() { return _userIds.GetRange(200, 200); }
リリースビルドとして実行すると無事に実行結果が表示されます。
まとめ
LINQPadで実行する方法、ふと忘れるときがあるのですがやってることは普通です。