LoginSignup
37
41

【.NET/C#】BenchmarkDotNetを使って、メソッドのパフォーマンスを簡単に集計する

Last updated at Posted at 2021-04-17

概要

BenchmarkDotNetの紹介と使い方を掲載する。

BenchmarkDotNetはメソッドのパフォーマンスを簡単に計測できるライブラリです。
統計的な情報をレポートしてくれるため、関数のパフォーマンスを調査する業務で役立ちそう。

目次

BenchmarkDotNetとは?

BenchmarkDotNetは、メソッドをベンチマークに変換し、そのパフォーマンスを追跡し、
再現性のある測定実験を共有するのに役立ちます。

上記はGitHubの記載内容の機械翻訳

サポート対象は下記。.NET系なら大体使えそう。
本記事ではC#で実装する。

Projects: classic and modern with PackageReferences
Runtimes: Full .NET Framework (4.6+), .NET Core (2.0+), Mono, CoreRT
OS: Windows, Linux, MacOS
Languages: C#, F#, VB

エクスポート機能も充実している。

設定手順

下記ドキュメントのOverViewに従って、パフォーマンス計測を行う。
BenchmarkDotNetの公式ドキュメント

1.NuGetパッケージをインストール

Visual Studioを立ち上げ、「コンソールアプリケーション」を選択する。
下記のNuGetパッケージを追加する。
NuGetリンク
Nuget-Benchmark.png

2.計測対象のメソッドを含むクラスを作成する

扱う題材(計測対象)

stringとStringBuilderの文字列結合の処理時間を比較する。
期待結果は「StringBuilderの方がパフォーマンスが優れる」こと。
※下記サイトで既に検証済み
文字列処理を高速に行う

StringBuilderがパフォーマンスが優れる理由 下記の違いにより、扱う文字の数が増えるとStringBuilderの方がパフォーマンスに優れることが多い。 ・string型は編集不可なため、文字列操作の度に新たなインスタンスを作る。 ・StringBuilderは編集可能であるため、文字列操作で新たなインスタンスを作らない。 stringとStringBuilderの違い

計測対象のクラスを定義する

計測対象のメソッド含むクラスを定義する。
対象メソッドには[Benchmark]属性を指定する。

using BenchmarkDotNet.Attributes;
using System.Text;

public class StringConcatMesurement
{
    private int NumberOfItems = 200000;
    [Benchmark]
    public string WithStringBuilder()
    {
        var sb = new StringBuilder();
        for (int i = 0; i < NumberOfItems; i++)
        {
            sb.Append("1");
        }
        return sb.ToString();
    }
    [Benchmark]
    public string WithStringType()
    {
        string s = string.Empty;
        for (int i = 0; i < NumberOfItems; i++)
        {
            s += "1";
        }
        return s;
    }
}

メイン関数からコールする

using BenchmarkDotNet.Running;

class Program
{
    static void Main(string[] args)
    {
        var summary = BenchmarkRunner.Run<StringConcatMesurement>();
    }
}

これで準備完了!!実行する前に一点だけ注意事項。
[リリースビルド]で実行すること。
[デバックビルド]の場合は、下記のように実行時エラーになる。

BenchmarkDotNet-デバックモードはエラー.png

ちなみに、このままの状態で実行すると私の環境で10分以上掛かった。
ベンチマーク計測に正確さが必要ない場合、[ShortRunJob]属性をつけて下さい(後述の項 計測時間の短縮をする方法を参照)。

計測開始時

BenchmarkDotNet開始時画面.png

計測終了時

BenchmarkDotNet終了時の画面.png

計測結果を確認する

下記の結果がコンソールに表示される。
・実行環境
・統計情報の表(関数名、平均値、エラー、標準偏差)
・統計情報のラベルの説明

// * Summary *

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.867 (2004/?/20H1)
Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=3.1.402
  [Host]     : .NET Core 3.1.8 (CoreCLR 4.700.20.41105, CoreFX 4.700.20.41903), X64 RyuJIT
  DefaultJob : .NET Core 3.1.8 (CoreCLR 4.700.20.41105, CoreFX 4.700.20.41903), X64 RyuJIT


|            Method |           Mean |         Error |        StdDev |
|------------------ |---------------:|--------------:|--------------:|
| WithStringBuilder |       895.1 us |      15.62 us |      21.90 us |
|    WithStringType | 7,806,015.2 us | 155,282.10 us | 347,310.67 us |

// * Hints *
Outliers
  StringConcatMesurement.WithStringBuilder: Default -> 4 outliers were removed (978.11 us..1.02 ms)
  StringConcatMesurement.WithStringType: Default    -> 4 outliers were removed (9.74 s..10.10 s)

// * Legends *
  Mean   : Arithmetic mean of all measurements
  Error  : Half of 99.9% confidence interval
  StdDev : Standard deviation of all measurements
  1 us   : 1 Microsecond (0.000001 sec)

予想通りStringBuilderの方がパフォーマンスが優れる。
平均値で比較すると、約8721倍StringBuilderが速い。
標準偏差を見ても、stringに比べると計測結果のバラツキが小さいことが分かる。

余談
コンソール出力内容はログに記録されている。
ソリューションのRelease直下にBenchmarkDotNet.Artifactsフォルダ内部に下記のログファイルが存在する。
「ソリューション名.計測対象クラス名-(日時:YYYYYMMDDD-HHMMS).log」

計測結果サマリをカスタマイズする

サマリーは下記の詳細結果のうち、属性でどれを出すかを指定する。
デフォルトだと、平均値、エラー、標準偏差を表示する。
試しに最小値、最大値を追加する。

// * Detailed results *
StringConcatMesurement.WithStringBuilder: DefaultJob
Runtime = .NET Core 3.1.8 (CoreCLR 4.700.20.41105, CoreFX 4.700.20.41903), X64 RyuJIT; GC = Concurrent Workstation
Mean = 830.978 us, StdErr = 3.726 us (0.45%), N = 15, StdDev = 14.432 us
Min = 809.898 us, Q1 = 821.067 us, Median = 826.252 us, Q3 = 844.369 us, Max = 855.115 us
IQR = 23.303 us, LowerFence = 786.113 us, UpperFence = 879.323 us
ConfidenceInterval = [815.549 us; 846.406 us] (CI 99.9%), Margin = 15.428 us (1.86% of Mean)
Skewness = 0.28, Kurtosis = 1.64, MValue = 2
-------------------- Histogram --------------------
[802.218 us ; 834.536 us) | @@@@@@@@@@
[834.536 us ; 857.584 us) | @@@@@
---------------------------------------------------

StringConcatMesurement.WithStringType: DefaultJob
Runtime = .NET Core 3.1.8 (CoreCLR 4.700.20.41105, CoreFX 4.700.20.41903), X64 RyuJIT; GC = Concurrent Workstation
Mean = 7.206 s, StdErr = 0.051 s (0.70%), N = 95, StdDev = 0.493 s
Min = 6.459 s, Q1 = 6.804 s, Median = 7.101 s, Q3 = 7.443 s, Max = 8.521 s
IQR = 0.639 s, LowerFence = 5.846 s, UpperFence = 8.402 s
ConfidenceInterval = [7.034 s; 7.378 s] (CI 99.9%), Margin = 0.172 s (2.38% of Mean)
Skewness = 0.84, Kurtosis = 3.15, MValue = 2.93
-------------------- Histogram --------------------
[6.317 s ; 6.608 s) | @@@@
[6.608 s ; 6.891 s) | @@@@@@@@@@@@@@@@@@@@@@@@
[6.891 s ; 7.243 s) | @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
[7.243 s ; 7.605 s) | @@@@@@@@@@@@@@@@@@@
[7.605 s ; 7.892 s) | @@@@@@@
[7.892 s ; 8.269 s) | @@@@@@@
[8.269 s ; 8.663 s) | @@@
---------------------------------------------------

サマリーのカスタマイズ実装


[MinColumn,MaxColumn]//←新規追加
public class StringConcatMesurement
{
    private int NumberOfItems = 200000;
    [Benchmark]
    public string WithStringBuilder()
    {
    //略
    }
    [Benchmark]
    public string WithStringType()
    {
     //略
    }
}

カスタマイズ後のサマリー

最小値、最大値の項目が増えた。

|            Method |           Mean |         Error |        StdDev |            Min |            Max |
|------------------ |---------------:|--------------:|--------------:|---------------:|---------------:|
| WithStringBuilder |       831.0 us |      15.43 us |      14.43 us |       809.9 us |       855.1 us |
|    WithStringType | 7,205,897.0 us | 171,677.49 us | 492,574.91 us | 6,458,541.9 us | 8,520,846.9 us |

計測時間の短縮をする方法

関数のパフォーマンスを改善する場面などでは、正確性よりも試行回数が重視されると思う。
何も制限なしで使うと、文字列の結合の評価だけで約10分かかった。
とても待ってられない。
下記[ShortRunJob]属性指定することで、関数の試行回数(N=3)で固定されて計測時間を短縮できる。

[ShortRunJob]属性指定

[ShortRunJob]//←これ
[MinColumn, MaxColumn]
public class StringConcatMesurement
{
//(略)
}

[ShortRunJob]属性指定前の計測時間

Global total time: 00:10:46 (646.11 sec), executed benchmarks: 2

[ShortRunJob]属性指定後の計測時間

Global total time: 00:00:57 (57.23 sec), executed benchmarks: 2

メモリ使用量を計測する方法

下記[MemoryDiagnoser]属性指定することで、メモリ使用量を計測できる。

[MemoryDiagnoser]//←これ
[ShortRunJob]
[MinColumn, MaxColumn]
public class StringConcatMesurement
{
//(略)
}

下記表のAllocatedがメモリ使用量。

|            Method |           Mean |           Error |          StdDev |            Min |            Max |         Gen 0 |         Gen 1 |         Gen 2 |      Allocated |
|------------------ |---------------:|----------------:|----------------:|---------------:|---------------:|--------------:|--------------:|--------------:|---------------:|
| WithStringBuilder |       955.9 μs |        556.6 μs |        30.51 μs |       924.3 μs |       985.2 μs |      125.0000 |      124.0234 |      124.0234 |      784.04 KB |
|    WithStringType | 7,500,495.0 μs | 22,021,618.8 μs | 1,207,079.24 μs | 6,611,154.0 μs | 8,874,598.0 μs | 11078000.0000 | 10509000.0000 | 10509000.0000 | 39067409.16 KB |

エクスポートする方法

出力形式を属性指定することでエクスポートが可能。形式の指定は下記のリンクを参考に。
エクスポート機能(リンク)も充実している。

HTMLでエクスポートした例は下記。

[HtmlExporter]//←これ
[MemoryDiagnoser]
[ShortRunJob]
[MinColumn, MaxColumn]
public class StringConcatMesurement
{
//(略)
}

コンソールに出力先が表示される。

// ***** BenchmarkRunner: Finish  *****

// * Export *
  BenchmarkDotNet.Artifacts\results\StudyBenchmarkDotNet.StringConcatMesurement-report.csv
  BenchmarkDotNet.Artifacts\results\StudyBenchmarkDotNet.StringConcatMesurement-report-github.md
  BenchmarkDotNet.Artifacts\results\StudyBenchmarkDotNet.StringConcatMesurement-report.html

HTMLファイルを開くと、こんな感じに出力される。

image.png

参考にしたサイト

BenchmarkDotNetの公式ドキュメント
How to benchmark C# code using BenchmarkDotNet
@neueccさんが書いたベンチマークの測り方
文字列処理を高速に行う
stringとStringBuilderの違い

37
41
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
37
41