C#でStopwatchを使った時間計測を1行でできるようにする

ある特定の区間の実行時間を測定したい事があると思います。(そして結果をパワポやエクセルに張り付けて報告しますね?

その場合、一般的にはStopwatch(System.Diagnostics名前空間内)を使って以下コードを記述していると思います。

// 典型的な時間計測のコード
public static void Main(string[] args)
{
    // 千回実行して合計時間を取る
    var sw = new Stopwatch();
    for (int i = 0; i < 10000; i++)
    {
        sw.Start();
        // 計測したい処理
        Foo();
        sw.Stop();
    }
    Trace.WriteLine($"実行時間は {TimeSpan.FromTicks(sw.ElapsedTicks)} ミリ秒でした");
}

public static void Foo() => // 少し時間のかかる処理

個人的に、この定型的な記述を毎回書くのが非常にだるいので計測を1行でできるようにライブラリを作成したいと思います。

というか、わざわざそんなことしないでもC#でパフォーマンス・ボトルネックを計測するには(VisalStudio2017だと)画面上部のメニューから「分析」>「パフォーマンス プロファイラー」を選んで実行すれば処理時間が簡単に計測できます。負荷が高い順に表示されます。

確認環境

実行および確認環境は以下の通りです。

  • Windows10
  • VisualStudio2017
  • C# 7.2
  • .NET Framework 4.7.2

時間計測用のクラス

特に説明は無いです。以下を自分のコードにコピペします。コピペしたら次の使い方を参照方。

// 簡単に時間計測用をするためのクラス
public static class StopwatchEx
{
    public static TimeSpan Context(Action f, int count = 1)
    {
        var sw = new Stopwatch();
        for (int i = 0; i < count; i++)
        {
            sw.Start();
            f();
            sw.Stop();
        }

        return TimeSpan.FromTicks(sw.ElapsedTicks);
    }

    public static TimeSpan Context<TResult>(Func<TResult> f, int count = 1)
    {
        var sw = new Stopwatch();
        sw.Reset();
        for (int i = 0; i < count; i++)
        {
            sw.Start();
            TResult restul = f(); // 読み捨て
            sw.Stop();
        }

        return TimeSpan.FromTicks(sw.ElapsedTicks);
    }
}

回数が指定されれば指定回数分のデリゲートを実行して合計時間を取得します。指定しない場合1回の実行時間の計測になります。

使い方

使用方法は以下の通り。

// つかいかた
public static void Main(string[] args)
{
    // (1) 千回実行した時間を計測(対象に引数戻り値も無いとき
    TimeSpan elapsed = StopwatchEx.Context(Foo, 1000);
    Trace.WriteLine($"実行時間は {elapsed.TotalMilliseconds} ミリ秒でした");

    // (2) 千回実行した時間を計測(対象に戻り値だけある時
    elapsed = StopwatchEx.Context(Foo2, 1000);
    Trace.WriteLine($"実行時間は {elapsed.TotalMilliseconds} ミリ秒でした");

    // (3) 千回実行した時間を計測(引数がいくつかあるとき
    int a = 1;
    string msg = "asdf";
    elapsed = StopwatchEx.Context(()=> { Foo3(a, msg); }, 1000);
    Trace.WriteLine($"実行時間は {elapsed.TotalMilliseconds} ミリ秒でした");
}

public static void Foo() => // 少し時間のかかる処理
public static int Foo2() => // 少し時間のかかる処理その(2)
public static int Foo2(int i) => // 少し時間のかかる処理その(3)

引数がある時はラムダ式を作って渡してください。

(3)のケースですが、(1), (2) に比べて微妙にオーバーヘッドが出るので正確な時間を図りたいという目的であれば少し構造を変更する必要があります。(まぁWindows上のC#で正確な時間の測定という定義がいまいち分からないですが…

ご注意

簡単に書ける反面、(当然ですが)デリゲートの呼び出しコストが僅かにかかります。(ただし、数千~1万回程度じゃ誤差の範囲です。

ごく短い処理を10億回実行した経過時間を得るなどの用途に微妙には向いていない事をご注意ください。

以上です。