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?
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);
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.
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);
}
Utilisez la classe chronomètre
System.Diagnostics.Stopwatch est conçu pour cette tâche.
Le chronomètre convient, mais bouclez le travail 10 ^ 6 fois, puis divisez-le par 10 ^ 6. Vous aurez beaucoup plus de précision.
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)