tech.guitarrapc.cóm

Technical updates

LINQPadでBenchmarkDotNetを使う

dotnet/BenchmarkDotNetは.NETコードのベンチマークライブラリで、デファクトスタンダードとして使われています。クラスやアセンブリ単位に実行したり、ベンチマークの試行回数調整やパラメーターセットアップも容易で非常に使いやすく、私自身もよく使っています。

さて、ライブラリを書いているとは主にコンソールアプリケーションからベンチマークを実行するのですが、サクッとコード断片をLINQPadで書くついでにベンチマークを取りたいことがあります。今回はLINQPadでBenchmarkDotNetを使う方法を紹介します。

LINQPadでBenchmarkDotNetを使う

LINQPadで実行するとResultsペインでベンチマーク結果が表示されます。この状態を目指しましょう。

image

LINQPadにBenchmarkDotNetを追加する

LINQPadにBenchmarkDotNetを追加するには、NuGetパッケージを追加します。F4 > Add NuGetを選択し、BenchmarkDotNetを検索して追加します。

image

ベンチーマークコードを書く

リストから特定のレンジを拾うのにパフォーマンスがいいのはどのような書き方かを調べるためのベンチマークを書いてみましょう。

  1. SkipAndTake: SkipTakeを使って範囲を取得
  2. Take: Takeの範囲指定を使って範囲を取得
  3. 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ビルドで実行するとちゃんと怒ってくれます、便利。

image

リリースビルドとして実行するには、Shift + Alt + Oで最適化を有効にしてF5実行します。画面右下の/o+をクリックしてもいいですね。

image

実行すると無事に実行結果が表示されます。

image

クラスを書かずに実行する

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

リリースビルドとして実行すると無事に実行結果が表示されます。

image

まとめ

LINQPadで実行する方法、ふと忘れるときがあるのですがやってることは普通です。

参考


  1. これはLINQPadのフォーラムにある方法です