Comment mesurer la performance du code in.NET Je ne sais pas.
je fais un benchmarking très rapide et sale sur une seule ligne de code C en utilisant DateTime:
long lStart = DateTime.Now.Ticks;
// do something
long lFinish = DateTime.Now.Ticks;
Le problème est dans les résultats:
Start Time [633679466564559902] Finish Time [633679466564559902] Start Time [633679466564569917] Finish Time [633679466564569917] Start Time [633679466564579932] Finish Time [633679466564579932]
...et ainsi de suite.
étant donné que les heures de départ et d'arrivée sont identiques, Ticks n'est évidemment pas assez granulaire.
alors, comment puis-je mieux mesurer le rendement?
18 réponses
la classe Stopwatch
, disponible depuis .NET 2.0, est la meilleure solution. Il s'agit d'un compteur de très haute performance précis à des fractions de milliseconde.
Jetez un oeil à la MSDN documentation , qui est assez clair.
modifier: comme précédemment suggéré, il est également conseillé d'exécuter votre code un certain nombre de fois afin d'obtenir un temps moyen raisonnable.
exécutez votre code à plusieurs reprises. Le problème semble être que votre code s'exécute beaucoup plus rapidement que la granularité de votre instrument de mesure. La solution la plus simple est d'exécuter votre code plusieurs, plusieurs fois (des milliers, peut-être des millions) et ensuite de calculer le temps moyen d'exécution.
éditer: aussi, en raison de la nature des compilateurs actuels d'optimisation (et des Machines virtuelles telles que le CLR et le JVM), il peut être très trompeur de mesurer la vitesse d'exécution des lignes simples de code, puisque la mesure peut influencer la vitesse beaucoup. Une bien meilleure approche consisterait à dresser le profil de l'ensemble du système (ou au moins des blocs plus grands) et à vérifier où se trouvent les goulets d'étranglement.
je trouve que ces utiles
http://accelero.codeplex.com/SourceControl/changeset/view/22633#290971 http://accelero.codeplex.com/SourceControl/changeset/view/22633#290973 http://accelero.codeplex.com/SourceControl/changeset/view/22633#290972
TickTimer est une copie coupée du chronomètre qui démarre lors de la construction et ne supporte pas le redémarrage. Il informez-vous également si le matériel actuel ne supporte pas le chronométrage à haute résolution (le chronomètre avale ce problème)
Si ce
var tickTimer = new TickTimer();
//call a method that takes some time
DoStuff();
tickTimer.Stop();
Debug.WriteLine("Elapsed HighResElapsedTicks " + tickTimer.HighResElapsedTicks);
Debug.WriteLine("Elapsed DateTimeElapsedTicks " + tickTimer.DateTimeElapsedTicks);
Debug.WriteLine("Elapsed ElapsedMilliseconds " + tickTimer.ElapsedMilliseconds);
Debug.WriteLine("Start Time " + new DateTime(tickTimer.DateTimeUtcStartTicks).ToLocalTime().ToLongTimeString());
affichera ce
Elapsed HighResElapsedTicks 10022886
Elapsed DateTimeElapsedTicks 41896
Elapsed ElapsedMilliseconds 4.18966178849554
Start Time 11:44:58
DebugTimer est un wrapper pour TickTimer qui écrira le résultat à déboguer. (note: it supports the jetable pattern)
Si ce
using (new DebugTimer("DoStuff"))
{
//call a method that takes some time
DoStuff();
}
affichera ceci dans la fenêtre de débogage
DoStuff: Total 3.6299 ms
IterationDebugTimer est pour chronométrer combien de temps il faut pour exécuter une opération plusieurs fois et écrire le résultat pour déboguer. Il effectuera également un lancement initial qui n'est pas inclus afin d'ignorer le temps de démarrage. (note: it supports the jetable pattern)
Si ce
int x;
using (var iterationDebugTimer = new IterationDebugTimer("Add", 100000))
{
iterationDebugTimer.Run(() =>
{
x = 1+4;
});
}
affichera ce
Add: Iterations 100000
Total 1.198540 ms
Single 0.000012 ms
juste pour ajouter à ce que d'autres ont déjà dit à propos de L'utilisation du chronomètre et de la mesure des moyennes.
assurez-vous d'appeler votre méthode avant de mesurer. Sinon, vous mesurerez le temps nécessaire pour compiler le code JIT. Cela pourrait fausser vos chiffres de manière significative.
aussi, assurez-vous de mesurer le code de mode de publication car les optimisations sont désactivées par défaut pour les constructions de débogage. Accorder le code de débogage est inutile imho.
Et assurez-vous de mesurer ce que vous voulez réellement mesurer. Lorsque les optimisations se déclenchent, le compilateur / JIT peut réorganiser le code ou le supprimer entièrement, de sorte que vous pouvez finir par mesurer quelque chose d'un peu différent de ce qui est prévu. Au moins, jetez un coup d'oeil au code généré pour vous assurer que le code n'a pas été dépouillé.
en Fonction de ce que vous essayez de mesurer gardez à l'esprit, qu'un réel système mettra l'accent sur l'exécution différemment qu'une application de test. Quelque les problèmes de performance sont liés, par exemple, à la façon dont les objets sont ramassés. Ces problèmes n'apparaissent généralement pas dans une simple application de test.
en fait, le meilleur conseil est de mesurer des systèmes réels avec des données réelles car les tests de bac à sable peuvent s'avérer très imprécis.
vous pouvez utiliser le Stopwatch
, en supposant que vous utilisez .NET 2.0 ou plus récent.
System.Diagnostics.Stopwatch.StartNew();
la classe Stopwatch
a aussi le champ public en lecture seule IsHighResolution
qui vous indiquera si le chronomètre est basé sur un compteur de performance à haute résolution. Si ce n'est pas le cas, il est basé sur la minuterie du système.
Je ne sais pas ce qu'il faut pour que le chronomètre soit basé sur un compteur de performance à haute résolution. Il y a quelques Appels API mais je me dis que si le chronomètre n'utilise pas une haute résolution, alors L'API n'est probablement pas là.
voir la réponse à est DateTime.Maintenant, la meilleure façon de mesurer le rendement d'une fonction? pour une explication ou lire mon post de blog sur la mesure de la performance
le problème est que DateTime a une résolution d'environ 15ms, il ne peut pas être plus précis que cela. Chronomètre, cependant, peut.
Voici une belle écriture au MSDN sur la façon de mettre en œuvre une mise à jour continue, fournisseur de temps à haute résolution pour Windows
voici le exemple de code source pour l'article (C++).
échantillon pour Stopwatch
classe
using System.Diagnostics;
......
...
..
Stopwatch sw = new Stopwatch();
sw.Start();
//Your Code Here
sw.Stop();
Console.WriteLine("Elapsed={0}",sw.Elapsed);
https://andreyakinshin.gitbooks.io/performancebookdotnet/content/science/microbenchmarking.html
https://github.com/PerfDotNet/BenchmarkDotNet
" en effet, microbencmarking est très difficile. Si une opération prend 10-100ns, la mesure de l'opération est un grand défi. Je vous suggère D'utiliser BenchmarkDotNet pour vos benchmarks. C'est une bibliothèque qui peut vous aider à faire un honnête référence et d'obtenir des mesures avec une bonne précision. Bien sûr, vous pouvez écrire de référence, sans autres bibliothèques. Dans cette section, nous expliquons pourquoi il est probablement une mauvaise idée et ce que vous devriez savoir avant de commencer."
cet article de projet de code montre comment utiliser le minuteur haute performance pour mesurer la vitesse d'exécution de votre code:
http://www.codeproject.com/KB/cs/highperformancetimercshar.aspx
Ici vous pouvez trouver un certain nombre de l'open source C# profileurs:
une autre option consiste à insérer automatiquement le code de minuterie avec Fody . Cela rend votre code beaucoup plus facile à lire car il sépare vos préoccupations transversales. Je pense que c'est proche de ce qu'on appelle Programmation Orientée Vers L'Aspect , mais fait au moment de la post-compilation.
voir https://github.com/Fody/MethodTimer pour l'addon de fody qui fait chronométrage de méthode.
citant du Readme:
avec un intercepteur, quelque part dans votre assemblage:
public static class MethodTimeLogger {
public static void Log(MethodBase methodBase, long milliseconds)
{
//Do some logging here
}
}
votre code,
public class MyClass
{
[Time]
public void MyMethod()
{
//Some code u are curious how long it takes
Console.WriteLine("Hello");
}
}
est compilé pour ceci:
public class MyClass
{
public void MyMethod()
{
var stopwatch = Stopwatch.StartNew();
try
{
//Some code u are curious how long it takes
Console.WriteLine("Hello");
}
finally
{
stopwatch.Stop();
MethodTimeLogger.Log(methodof(MyClass.MyMethod), stopwatch.ElapsedMilliseconds);
}
}
}
Jetable style Stopwatch
qui fonctionne le mieux pour moi.
class VWatch : IDisposable {
Stopwatch watch = new Stopwatch();
public VWatch() {
this.watch.Start();
}
public void Dispose() {
this.watch.Stop();
Console.WriteLine("Finished. Elapsed={0}", this.watch.Elapsed);
}
}
et ensuite:
using (new VWatch()) {
/// do something for time measurement
}
parfois, il peut être préférable de regarder pourquoi vous avez besoin de chronométrer l'opération? Est-il lent? Ou vous êtes simplement curieux? Première règle de l'optimisation est "ne fais pas cela". Ainsi, selon ce que vous mesurez réellement, pourrait changer l'opinion sur ce qui est le mieux adapté à la tâche.
plus facile à utiliser un profileur comme profileur de Performance de fourmis , ou l'un des autres qui sont disponibles.
j'ai fait une extension qui renvoie des millisecondes de tiques.
public static int GetTotalRunningTimeInMilliseconds(this DateTime start)
{
var endTicks = DateTime.Now.Ticks - start.Ticks;
return TimeSpan.FromTicks(endTicks).Milliseconds;
}
Utilisation:
var start = DateTime.Now;
//...your long running code here
var endTime = start.GetTotalRunningTimeInMilliseconds();
j'ai fait une méthode très simple qui mesure la vitesse d'exécution d'une Action , qui a pour moi l'avantage que je peux la réutiliser chaque fois que j'en ai besoin, et quel que soit le code que je dois mesurer.
pour moi un DateTime était suffisant, mais il est facilement adaptable de DateTime à Chronomètre .
public static TimeSpan MeasureTime(Action action)
{
DateTime start = DateTime.Now;
if (action == null)
{
throw new ArgumentNullException("action");
}
try
{
action();
}
catch (Exception ex)
{
Debugger.Log(1, "Measuring",ex.ToString());
}
return DateTime.Now - start;
}
Comment l'utiliser?:
private static void StressTest()
{
List<TimeSpan> tss = new List<TimeSpan>();
for (int i = 0; i < 100; i++)
{
// here is the measuring:
var ts = MeasureTime(() => instance.Method("param1"));
tss.Add(ts);
}
Console.WriteLine("Max: {0}", tss.Max());
Console.WriteLine("Min: {0}", tss.Min());
Console.WriteLine("Avg: {0}", TimeSpan.FromMilliseconds(tss.Average(i => i.TotalMilliseconds)));
}
ou:
var ts = MeasureTime(() =>
{
// Some intensive stuff here
int a = 1;
// more here
int b = 2;
// and so on
});
pour mesurer la performance avec différence entre les mesures, j'utilise cette classe. La classe Chronomètre n'a pas la méthode Split
.
/// <summary>
/// Stopwatch like class that keeps track of timelapses.
/// Probably low-res because of the usage of DateTime.
/// </summary>
public class ChronoMeter
{
/// <summary>
/// The name given when the Chronometer was constructed.
/// </summary>
public string Name { get; private set; }
/// <summary>
/// The moment in time Start was called.
/// </summary>
public DateTime Started { get; private set; }
/// <summary>
/// All time recordings are added to this list by calling Split and Stop.
/// </summary>
public List<ChronoRecord> Records { get; private set; }
private readonly Stopwatch _stopWatch = new Stopwatch();
private bool _hasBeenStopped = false;
/// <summary>
/// Constrcutor
/// </summary>
/// <param name="pName">The name is used in logging</param>
/// <param name="pLoggingType">The type of logging appriate for the information yielded by this time recording.</param>
public ChronoMeter(string pName)
{
Name = pName;
Records = new List<ChronoRecord>();
}
/// <summary>
/// Not calling Stop is bad practise. Therefore a little safety net zo the end is still recorderd.
/// Keep in mind that the garbase collector invokes the destructor, so the moment of time probably doesn't make much sense.
/// It is more to notify that you should have used Stop for the latest split.
/// </summary>
~ChronoMeter()
{
if (!_hasBeenStopped)
{
Stop("Destructor safety net");
}
}
/// <summary>
/// TimeElapsedSinceStart of a ChronoRecord is relative to the moment ChronoMeter was started by calling this function.
/// </summary>
public void Start()
{
_stopWatch.Start();
Started = DateTime.Now;
}
/// <summary>
/// Splits the timerecording and add a record of this moment to the list of split records.
/// </summary>
/// <param name="pSplitName"></param>
public void Split(string pSplitName)
{
_stopWatch.Stop();
var created = Started + _stopWatch.Elapsed;
var previousRecord = Records.LastOrDefault();
Records.Add(new ChronoRecord(pSplitName, Started, created, previousRecord));
_stopWatch.Start();
}
/// <summary>
/// Indicates you are done and the records will be written to the log.
/// </summary>
public void Stop(string pSplitName)
{
Split(pSplitName);
_stopWatch.Stop();
_hasBeenStopped = true;
}
public class ChronoRecord
{
public string Name { get; private set; }
public TimeSpan TimeElapsedSinceStart { get; private set; }
public TimeSpan TimeElapsedSincePrevious { get; private set; }
public DateTime Start { get; private set; }
public DateTime Created { get; private set; }
public ChronoRecord(string pName, DateTime pStartDateTime, DateTime pCreated, ChronoRecord pPreviousRecord=null)
{
if (pCreated == default(DateTime)) //Ignore DefaultDateTimeComparison
{
pCreated = DateTime.Now;
}
Created = pCreated;
Name = pName;
Start = pStartDateTime;
TimeElapsedSinceStart = Created - Start;
if (pPreviousRecord != null)
{
TimeElapsedSincePrevious = Created - pPreviousRecord.Created;
}
else
{
TimeElapsedSincePrevious = TimeElapsedSinceStart;
}
}
}
}