web-dev-qa-db-fra.com

Mesure exacte du temps pour les tests de performance

Quelle est la manière la plus exacte de voir combien de temps quelque chose, par exemple un appel de méthode, a pris du code?

Je suppose que le plus simple et le plus rapide est le suivant:

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

Mais comment est-ce exact? Y a-t-il de meilleurs moyens?

293
Svish

Un meilleur moyen consiste à utiliser la classe Stopwatch:

using System.Diagnostics;
// ...

Stopwatch sw = new Stopwatch();

sw.Start();

// ...

sw.Stop();

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

Comme d'autres l'ont dit, Stopwatch est une bonne classe à utiliser ici. Vous pouvez envelopper dans une méthode utile:

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

(Notez l’utilisation de Stopwatch.StartNew(). Je préfère cela plutôt que de créer un chronomètre puis d’appeler Start() en termes de simplicité.) Évidemment, il s’agit d’invoquer un délégué, mais dans la grande majorité des cas, ne sera pas pertinent. Vous écririez alors:

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

Vous pouvez même créer une interface ITimer pour cela, avec les implémentations de StopwatchTimer,CpuTimer etc si elles sont disponibles.

161
Jon Skeet

Comme d'autres l'ont dit, Stopwatch devrait être le bon outil pour cela. Cependant, il peut y avoir peu d’améliorations, voir ce fil de discussion spécifiquement: Benchmarking de petits exemples de code en C #, cette implémentation peut-elle être améliorée? .

J'ai vu des conseils utiles de Thomas Maierhofer ici

Fondamentalement, son code ressemble à:

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

Une autre approche consiste à utiliser Process.TotalProcessTime pour mesurer le temps pendant lequel la CPU a été occupée et exécuter le code/processus même , comme indiqué ici Cela peut refléter un scénario plus réel, car aucun autre processus n’affecte la mesure. Il fait quelque chose comme:

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

Une implémentation nue et détaillée du exemple est disponible ici.

J'ai écrit une classe d'assistance pour exécuter les deux de manière simple:

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

Il suffit d'appeler

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

}, 10000000);

ou

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

}, 10000000);

La dernière partie de la Clock est la partie la plus délicate. Si vous souhaitez afficher le timing final, c'est à vous de choisir le type de timing souhaité. J'ai écrit une méthode d'extension NormalizedMean qui vous donne la moyenne des timings lus en ignorant le bruit. Je veux dire que je calcule l'écart de chaque minutage par rapport à la moyenne réelle, et puis je supprime les valeurs qui étaient plus éloignées (uniquement les plus lentes) de la moyenne de déviation (appelée déviation absolue; notez que ce n'est pas souvent le cas écart type) et renvoient enfin la moyenne des valeurs restantes. Cela signifie, par exemple, que si les valeurs chronométrées sont { 1, 2, 3, 2, 100 } (en ms ou quoi que ce soit d'autre), il rejette 100 et renvoie la moyenne de { 1, 2, 3, 2 } qui est 2. Ou, si les durées sont { 240, 220, 200, 220, 220, 270 }, il écarte 270 et renvoie la moyenne de { 240, 220, 200, 220, 220 } qui est 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

Utilisez la classe chronomètre

13
Mehrdad Afshari

System.Diagnostics.Stopwatch est conçu pour cette tâche.

12
Dimi Takis

Le chronomètre convient, mais bouclez le travail 10 ^ 6 fois, puis divisez-le par 10 ^ 6. Vous aurez beaucoup plus de précision.

5
Mike Dunlavey

J'utilise ceci:

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

De mon blog: Mesure du temps C # pour les tests de performance (Pas en anglais)

3
altansezerayan