wake-up-neo.com

Genaue Zeitmessung für Leistungstests

Was ist die genaueste Methode, um zu sehen, wie lange etwas, zum Beispiel ein Methodenaufruf, Code benötigt hat?

Das einfachste und schnellste, was ich vermuten würde, ist folgendes:

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

Aber wie genau ist das? Gibt es bessere Wege?

293
Svish

Eine bessere Möglichkeit ist die Verwendung der Stoppuhr-Klasse:

using System.Diagnostics;
// ...

Stopwatch sw = new Stopwatch();

sw.Start();

// ...

sw.Stop();

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

Wie andere gesagt haben, ist Stopwatch eine gute Klasse, um hier zu verwenden. Sie können es in einer hilfreichen 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 es aus Gründen der Einfachheit vor, eine Stoppuhr zu erstellen und dann Start() aufzurufen.) Offensichtlich führt dies dazu, dass ein Delegierter aufgerufen wird Die meisten Fälle, die nicht relevant sind. Sie würden dann schreiben:

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

Sie können hierfür sogar eine ITimer-Schnittstelle erstellen, mit Implementierungen von StopwatchTimer,CpuTimer usw., sofern verfügbar.

161
Jon Skeet

Wie schon andere gesagt haben, sollte Stopwatch das richtige Werkzeug dafür sein. Es gibt jedoch nur wenige Verbesserungen, siehe diesen Thread speziell: Benchmarking kleiner Codebeispiele in C #, kann diese Implementierung verbessert werden? .

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

Grundsätzlich sieht sein Code 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 ist, sich auf Process.TotalProcessTime um zu messen, wie lange die CPU beschäftigt war Ausführen des Codes/Prozesses, wie hier gezeigt Dies kann widerspiegeln realeres Szenario, da kein anderer Prozess die Messung beeinflusst. Es macht so etwas wie:

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

Eine nackte, detaillierte Implementierung des etwas kann hier gefunden werden.

Ich habe eine Helferklasse geschrieben, um beides auf einfache Art und Weise auszuführen:

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

Ruf einfach an

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

}, 10000000);

oder

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

}, 10000000);

Der letzte Teil von Clock ist der knifflige Teil. Wenn Sie das endgültige Timing anzeigen möchten, können Sie das gewünschte Timing auswählen. Ich habe eine Erweiterungsmethode NormalizedMean geschrieben, die den Mittelwert der gelesenen Zeiten angibt das Rauschen verwerfen. Ich meine, ich berechne die Abweichung jeder Zeit vom tatsächlichen Mittelwert und dann Ich verwerfe die Werte, die weiter entfernt waren (nur die langsameren), vom Mittelwert der Abweichung (absolute Abweichung genannt; beachte, dass es nicht die oft gehörte Standardabweichung ist ) und geben schließlich den Mittelwert der verbleibenden Werte zurück. Dies bedeutet zum Beispiel, wenn zeitgesteuerte Werte { 1, 2, 3, 2, 100 } (in ms oder was auch immer), wirft 100 und liefert den Mittelwert von { 1, 2, 3, 2 } welches ist 2. Oder wenn Timings sind { 240, 220, 200, 220, 220, 270 }, wirft es 270 und liefert den Mittelwert von { 240, 220, 200, 220, 220 } welches 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);
}
74
nawfal

Verwenden Sie die Klasse Stoppuhr

13
Mehrdad Afshari

System.Diagnostics.Stopwatch wurde für diese Aufgabe entwickelt.

12
Dimi Takis

Die Stoppuhr ist in Ordnung, aber wiederholen Sie die Arbeit 10 ^ 6 Mal und dividieren Sie sie dann durch 10 ^ 6. Sie erhalten viel mehr Präzision.

5
Mike Dunlavey

Ich benutze das:

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

Aus meinem Blog: C # -Zeitmessung für Leistungstests (Nicht auf Englisch)

3
altansezerayan