web-dev-qa-db-ja.com

性能試験のための正確な時間測定

メソッド呼び出しなど、コードに取り込まれた時間を知るための最も正確な方法は何ですか?

私が推測する最も簡単で最速のものはこれです:

DateTime start = DateTime.Now;
{
    // Do some work
}
TimeSpan timeItTook = DateTime.Now - start;

しかし、これはどの程度正確ですか?もっと良い方法はありますか?

286
Svish

より良い方法はStopwatchクラスを使うことです:

using System.Diagnostics;
// ...

Stopwatch sw = new Stopwatch();

sw.Start();

// ...

sw.Stop();

Console.WriteLine("Elapsed={0}",sw.Elapsed);
532

他の人が言っているように、Stopwatchはここで使うのに良いクラスです。あなたは役に立つ方法でそれを包むことができます:

public static TimeSpan Time(Action action)
{
    Stopwatch stopwatch = Stopwatch.StartNew();
    action();
    stopwatch.Stop();
    return stopwatch.Elapsed;
}

Stopwatch.StartNew()の使用に注意してください。私はこれをストップウォッチを作成してからStart()を呼び出すほうが簡単です。)明らかにこれはデリゲートを呼び出すヒットを招きますが、大抵の場合は関係ないでしょう。あなたはそれから書くでしょう:

TimeSpan time = StopwatchUtil.Time(() =>
{
    // Do some work
});

利用可能な場合はStopwatchTimer,ITimerなどの実装を使用して、CpuTimerインターフェースを作成することもできます。

159
Jon Skeet

他の人が言ったように、Stopwatchはこれのための正しいツールであるべきです。ただし、このスレッドを参照してください。 C#で小さなコードサンプルをベンチマークする、この実装を改善できますか。

Thomas Maierhoferがここで役に立つヒントをいくつか見ました

基本的に彼のコードはこんな感じです:

//prevent the JIT Compiler from optimizing Fkt calls away
long seed = Environment.TickCount;

//use the second Core/Processor for the test
Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(2);

//prevent "Normal" Processes from interrupting Threads
Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;

//prevent "Normal" Threads from interrupting this thread
Thread.CurrentThread.Priority = ThreadPriority.Highest;

//warm up
method();

var stopwatch = new Stopwatch()
for (int i = 0; i < repetitions; i++)
{
    stopwatch.Reset();
    stopwatch.Start();
    for (int j = 0; j < iterations; j++)
        method();
    stopwatch.Stop();
    print stopwatch.Elapsed.TotalMilliseconds;
}

もう1つの方法は、CPUがビジー状態を維持している時間の測定にProcess.TotalProcessTimeを使用することです。{コードまたはプロセスの実行中ここに表示 他のプロセスは測定に影響を及ぼさないのでシナリオ。それは以下のようになります。

 var start = Process.GetCurrentProcess().TotalProcessorTime;
 method();
 var stop = Process.GetCurrentProcess().TotalProcessorTime;
 print (end - begin).TotalMilliseconds;

samethingの裸で詳細な実装はここにあります。

私は使いやすい方法で両方を実行するためのヘルパークラスを書きました:

public class Clock
{
    interface IStopwatch
    {
        bool IsRunning { get; }
        TimeSpan Elapsed { get; }

        void Start();
        void Stop();
        void Reset();
    }



    class TimeWatch : IStopwatch
    {
        Stopwatch stopwatch = new Stopwatch();

        public TimeSpan Elapsed
        {
            get { return stopwatch.Elapsed; }
        }

        public bool IsRunning
        {
            get { return stopwatch.IsRunning; }
        }



        public TimeWatch()
        {
            if (!Stopwatch.IsHighResolution)
                throw new NotSupportedException("Your hardware doesn't support high resolution counter");

            //prevent the JIT Compiler from optimizing Fkt calls away
            long seed = Environment.TickCount;

            //use the second Core/Processor for the test
            Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(2);

            //prevent "Normal" Processes from interrupting Threads
            Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;

            //prevent "Normal" Threads from interrupting this thread
            Thread.CurrentThread.Priority = ThreadPriority.Highest;
        }



        public void Start()
        {
            stopwatch.Start();
        }

        public void Stop()
        {
            stopwatch.Stop();
        }

        public void Reset()
        {
            stopwatch.Reset();
        }
    }



    class CpuWatch : IStopwatch
    {
        TimeSpan startTime;
        TimeSpan endTime;
        bool isRunning;



        public TimeSpan Elapsed
        {
            get
            {
                if (IsRunning)
                    throw new NotImplementedException("Getting elapsed span while watch is running is not implemented");

                return endTime - startTime;
            }
        }

        public bool IsRunning
        {
            get { return isRunning; }
        }



        public void Start()
        {
            startTime = Process.GetCurrentProcess().TotalProcessorTime;
            isRunning = true;
        }

        public void Stop()
        {
            endTime = Process.GetCurrentProcess().TotalProcessorTime;
            isRunning = false;
        }

        public void Reset()
        {
            startTime = TimeSpan.Zero;
            endTime = TimeSpan.Zero;
        }
    }



    public static void BenchmarkTime(Action action, int iterations = 10000)
    {
        Benchmark<TimeWatch>(action, iterations);
    }

    static void Benchmark<T>(Action action, int iterations) where T : IStopwatch, new()
    {
        //clean Garbage
        GC.Collect();

        //wait for the finalizer queue to empty
        GC.WaitForPendingFinalizers();

        //clean Garbage
        GC.Collect();

        //warm up
        action();

        var stopwatch = new T();
        var timings = new double[5];
        for (int i = 0; i < timings.Length; i++)
        {
            stopwatch.Reset();
            stopwatch.Start();
            for (int j = 0; j < iterations; j++)
                action();
            stopwatch.Stop();
            timings[i] = stopwatch.Elapsed.TotalMilliseconds;
            print timings[i];
        }
        print "normalized mean: " + timings.NormalizedMean().ToString();
    }

    public static void BenchmarkCpu(Action action, int iterations = 10000)
    {
        Benchmark<CpuWatch>(action, iterations);
    }
}

電話するだけ

Clock.BenchmarkTime(() =>
{
    //code

}, 10000000);

または

Clock.BenchmarkCpu(() =>
{
    //code

}, 10000000);

Clockの最後の部分はトリッキーな部分です。あなたが最終的なタイミングを表示したいならば、あなたが望むタイミングの種類を選ぶのはあなた次第です。読み取りタイミングの平均を与える拡張方法NormalizedMeanを書きました。{ノイズを捨てる _私は実際の平均からの各タイミングの偏差を計算し、そして次に私はより遠い値を捨てます偏差の平均から(より遅いものだけ)(絶対偏差と呼ばれます;それはよく聞かれる標準偏差ではないことに注意してください)、そして最後にの平均を返す残りの値これは、例えば、時間指定された値が{ 1, 2, 3, 2, 100 }(ミリ秒など)であれば、100を破棄し、{ 1, 2, 3, 2 }の平均、つまり2を返すことを意味します。あるいは、タイミングが{ 240, 220, 200, 220, 220, 270 }の場合、270を破棄し、{ 240, 220, 200, 220, 220 }の平均値220を返します。

public static double NormalizedMean(this ICollection<double> values)
{
    if (values.Count == 0)
        return double.NaN;

    var deviations = values.Deviations().ToArray();
    var meanDeviation = deviations.Sum(t => Math.Abs(t.Item2)) / values.Count;
    return deviations.Where(t => t.Item2 > 0 || Math.Abs(t.Item2) <= meanDeviation).Average(t => t.Item1);
}

public static IEnumerable<Tuple<double, double>> Deviations(this ICollection<double> values)
{
    if (values.Count == 0)
        yield break;

    var avg = values.Average();
    foreach (var d in values)
        yield return Tuple.Create(d, avg - d);
}
73
nawfal

ストップウォッチ クラスを使う

13
Mehrdad Afshari

System.Diagnostics.Stopwatchはこのタスク用に設計されています。

12
Dimi Takis

ストップウォッチは問題ありませんが、作業を10 ^ 6回ループしてから10 ^ 6で割ります。もっと正確になるでしょう。

5
Mike Dunlavey

私はこれを使っています:

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(myUrl);
System.Diagnostics.Stopwatch timer = new Stopwatch();

timer.Start();

HttpWebResponse response = (HttpWebResponse)request.GetResponse();

statusCode = response.StatusCode.ToString();

response.Close();

timer.Stop();

私のブログから: パフォーマンステストのためのC#時間測定 (英語以外)

3
altansezerayan