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($"実行時間は {sw.Elapsed.TotalMilliseconds)} ミリ秒でした");
}

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

定型的な処理なので毎回このコードを書くのは面倒なので計測を1行でできるようにライブラリを作成したいと思います。

ちなみに、コードを書かないでもC#でパフォーマンス・ボトルネックを計測するには(VisalStudio2017だと)画面上部のメニューから「分析」>「パフォーマンス プロファイラー」を選んで実行すれば処理時間が長い順に関数が表示されたりします。

確認環境

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

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

時間計測用のクラス

まずは、以下を自分のコードにコピペします。

using System;
using System.Diagnostics;

// 簡単に時間計測用をするためのクラス
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 sw.Elapsed;
    }

    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 sw.Elapsed;
    }
}

回数が指定されれば指定回数分のデリゲートを実行して合計時間を取得します。指定しない場合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億回実行した経過時間を得るなどの用途に微妙には向いていない事をご注意ください。

以上です。

関連リンク

より正確に計測したい場合 BenchmarkDotNet というライブラリを使用しましょう。

特にコードのパフォーマンス計測結果を公開する場合に以下のライブラリの使用を推奨します。

takap-tech.com