Точное измерение времени для тестирования производительности

Каков самый точный способ увидеть, как долго что-то, например вызов метода, принимал код?

Самый простой и быстрый, я думаю, это:

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

Но насколько это точно? Есть ли лучшие способы?

Ответ 1

Лучше всего использовать класс секундомера:

using System.Diagnostics;
// ...

Stopwatch sw = new Stopwatch();

sw.Start();

// ...

sw.Stop();

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

Ответ 2

Как говорили другие, 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
});

Вы можете даже сделать для этого интерфейс ITimer, с реализациями StopwatchTimer, CpuTimer и т.д., где это возможно.

Ответ 3

Как говорили другие, Stopwatch должен быть правильным инструментом для этого. В нем может быть сделано несколько улучшений, особенно в этой теме: Бенчмаркинг небольших образцов кода на С#, можно ли эту реализацию улучшить?.

Я видел несколько полезных советов от Томаса Майерхофера здесь

В основном его код выглядит так:

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

Другой подход заключается в том, чтобы полагаться на Process.TotalProcessTime для измерения того, как долго процессор остается занятым, выполняющим сам код/​​процесс, как показано здесь Это может отражать более реальный сценарий, поскольку никакой другой процесс не влияет на измерение. Он делает что-то вроде:

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

Голая, подробная реализация можно найти здесь.

Я написал класс-помощник для выполнения как простым способом:

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

Ответ 4

Используйте Stopwatch класс

Ответ 5

System.Diagnostics.Stopwatch предназначен для этой задачи.

Ответ 6

Секундомер в порядке, но цикл работы 10 ^ 6 раз, затем разделите на 10 ^ 6. Вы получите гораздо больше точности.

Ответ 7

Я использую это:

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

Из моего блога: С# Измерение времени для тестирования производительности (не на английском языке)

Ответ 8

Да, есть какая-то функция в ядре Windows

[System.Runtime.InteropServices.DllImport("KERNEL32")]
private static extern bool QueryPerformanceCounter(ref long lpPerformanceCount);

[System.Runtime.InteropServices.DllImport("KERNEL32")]
private static extern bool QueryPerformanceFrequency(ref long lpFrequency);

public static float CurrentSecond
{
    get
    {
        long current = 0;
        QueryPerformanceCounter(ref current);
        long frequency = 0;
        QueryPerformanceFrequency(ref frequency);
        return (float) current / (float) frequency;
    }
}