390 Stimmen

Genaue Zeitmessung für Leistungstests

Wie kann man am genauesten feststellen, wie lange etwas, z. B. ein Methodenaufruf, im Code gedauert hat?

Am einfachsten und schnellsten geht es wohl so:

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

Aber wie genau ist das? Gibt es bessere Methoden?

749voto

Philippe Leybaert Punkte 161931

Eine bessere Möglichkeit ist die Verwendung der Klasse Stopwatch:

using System.Diagnostics;
// ...

Stopwatch sw = new Stopwatch();

sw.Start();

// ...

sw.Stop();

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

196voto

Jon Skeet Punkte 1325502

Wie andere gesagt haben, Stopwatch ist eine gute Klasse, die hier verwendet werden kann. Sie können sie in eine hilfreiche Methode verpacken:

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

(Beachten Sie die Verwendung von Stopwatch.StartNew() . Ich ziehe dies der Erstellung einer Stoppuhr und dem anschließenden Aufruf von Start() der Einfachheit halber). Natürlich hat dies den Nachteil, dass ein Delegat aufgerufen werden muss, aber in den meisten Fällen wird das nicht relevant sein. Sie würden dann schreiben:

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

Sie könnten sogar eine ITimer Schnittstelle für diese, mit Implementierungen von StopwatchTimer, CpuTimer usw., sofern verfügbar.

93voto

nawfal Punkte 65966

Wie andere sagten, Stopwatch sollte das richtige Werkzeug dafür sein. Es können jedoch noch einige Verbesserungen vorgenommen werden, siehe insbesondere diesen Thread: Benchmarking kleiner Codebeispiele in C#, kann diese Implementierung verbessert werden? .

Ich habe einige nützliche Tipps gesehen von Thomas Maierhofer hier

Sein Code sieht im Wesentlichen so aus:

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

Ein anderer Ansatz besteht darin, sich auf Process.TotalProcessTime um zu messen, wie lange die CPU beschäftigt war Ausführen des Codes/Prozesses selbst , wie hier gezeigt Dies kann ein realistischeres Szenario widerspiegeln, da kein anderer Prozess die Messung beeinflusst. Es tut so etwas wie:

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

Eine nackte, detaillierte Umsetzung der kann hier gefunden werden.

Ich habe eine Hilfsklasse geschrieben, die beides auf einfache Art und Weise ermöglicht:

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

Einfach anrufen

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

}, 10000000);

o

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

}, 10000000);

Der letzte Teil der Clock ist der schwierige Teil. Wenn Sie das endgültige Timing anzeigen wollen, müssen Sie selbst entscheiden, welche Art von Timing Sie wollen. Ich habe eine Erweiterungsmethode geschrieben NormalizedMean der den Mittelwert der Lesezeiten angibt den Lärm zu verwerfen. Ich meine, ich berechne die Abweichung jeder Zeitmessung vom tatsächlichen Mittelwert, und dann verwerfe ich die Werte, die weiter (nur die langsameren) vom Mittelwert entfernt sind. Mittelwert der Abweichung (genannt absolute Abweichung; beachten Sie, dass es sich dabei nicht um die oft gehörte Standardabweichung handelt), und schließlich den Mittelwert der verbleibenden Werte zurückgeben. Das bedeutet, dass zum Beispiel, wenn die zeitlichen Werte { 1, 2, 3, 2, 100 } (in ms oder was auch immer), verwirft es 100 und gibt den Mittelwert von { 1, 2, 3, 2 } das ist 2 . Oder wenn die Zeitangaben { 240, 220, 200, 220, 220, 270 } verwirft sie 270 und gibt den Mittelwert von { 240, 220, 200, 220, 220 } das ist 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);
}

15voto

mmx Punkte 400975

を使用します。 Stoppuhr Klasse

13voto

Dimi Takis Punkte 4819

System.Diagnostics.Stopwatch ist für diese Aufgabe vorgesehen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X