web-dev-qa-db-fra.com

Environment.TickCount vs DateTime.Now

Est-il toujours correct d'utiliser Environment.TickCountpour calculer les intervalles de temps?

int start = Environment.TickCount;
// Do stuff
int duration = Environment.TickCount - start;
Console.WriteLine("That took " + duration " ms");

Parce que TickCount est signé et survolera après 25 jours (il faut 50 jours pour atteindre les 32 bits, mais vous devez supprimer le bit signé si vous voulez donner un sens aux mathématiques), il semble que ce soit trop risqué pour être utile.

J'utilise DateTime.Now au lieu. Est-ce la meilleure façon de procéder?

DateTime start = DateTime.Now;
// Do stuff
TimeSpan duration = DateTime.Now - start;
Console.WriteLine("That took " + duration.TotalMilliseconds + " ms");
61
Jon B

Utilisez la classe Chronomètre. Il existe un exemple décent sur msdn: http://msdn.Microsoft.com/en-us/library/system.diagnostics.stopwatch.aspx

    Stopwatch stopWatch = Stopwatch.StartNew();
    Thread.Sleep(10000);
    stopWatch.Stop();
    // Get the elapsed time as a TimeSpan value.
    TimeSpan ts = stopWatch.Elapsed;
68
Grzenio

Environment.TickCount est basé sur GetTickCount () fonction WinAPI. C'est en millisecondes Mais la précision réelle est d'environ 15,6 ms. Vous ne pouvez donc pas mesurer des intervalles de temps plus courts (ou vous obtiendrez 0)

Remarque: La valeur renvoyée est Int32, donc ce compteur survole chaque ~ 49,7 jours. Vous ne devriez pas l'utiliser pour mesurer de si longs intervalles.

DateTime.Ticks est basé sur GetSystemTimeAsFileTime () fonction WinAPI. C'est en 100 nanosecondes (dixièmes de microsecondes). La précision réelle de DateTime.Ticks dépend du système. Sur XP, l'incrément de l'horloge système est d'environ 15,6 ms, le même que dans Environment.TickCount. Sur Windows 7, sa précision est de 1 ms (tandis que celle de Environemnt.TickCount est toujours de 15,6 ms), mais si un schéma d'économie d'énergie est utilisé (généralement sur les ordinateurs portables), il peut également descendre à 15,6 ms.

Chronomètre est basé sur QueryPerformanceCounter () Fonction WinAPI (mais si le compteur de performances haute résolution n'est pas pris en charge par votre système, DateTime .Ticks est utilisé)

Avant d'utiliser StopWatch, notez deux problèmes:

  • il peut ne pas être fiable sur les systèmes multiprocesseurs (voir MS kb89598 , kb896256 )
  • cela peut ne pas être fiable si la fréquence du CPU varie (lire this article)

Vous pouvez évaluer la précision de votre système avec un test simple:

static void Main(string[] args)
{
    int xcnt = 0;
    long xdelta, xstart;
    xstart = DateTime.UtcNow.Ticks;
    do {
        xdelta = DateTime.UtcNow.Ticks - xstart;
        xcnt++;
    } while (xdelta == 0);

    Console.WriteLine("DateTime:\t{0} ms, in {1} cycles", xdelta / (10000.0), xcnt);

    int ycnt = 0, ystart;
    long ydelta;
    ystart = Environment.TickCount;
    do {
        ydelta = Environment.TickCount - ystart;
        ycnt++;
    } while (ydelta == 0);

    Console.WriteLine("Environment:\t{0} ms, in {1} cycles ", ydelta, ycnt);


    Stopwatch sw = new Stopwatch();
    int zcnt = 0;
    long zstart, zdelta;

    sw.Start();
    zstart = sw.ElapsedTicks; // This minimizes the difference (opposed to just using 0)
    do {
        zdelta = sw.ElapsedTicks - zstart;
        zcnt++;
    } while (zdelta == 0);
    sw.Stop();

    Console.WriteLine("StopWatch:\t{0} ms, in {1} cycles", (zdelta * 1000.0) / Stopwatch.Frequency, zcnt);
    Console.ReadKey();
}
87
mistika

Pourquoi vous inquiétez-vous du roulement? Tant que la durée que vous mesurez est inférieure à 24,9 jours et que vous calculez la durée relative, tout va bien. Peu importe la durée de fonctionnement du système, tant que vous ne vous préoccupez que de votre partie de ce temps d'exécution (par opposition à effectuer directement des comparaisons inférieures ou supérieures aux comparaisons sur les points de début et de fin). C'est à dire. cette:

 int before_rollover = Int32.MaxValue - 5;
 int after_rollover = Int32.MinValue + 7;
 int duration = after_rollover - before_rollover;
 Console.WriteLine("before_rollover: " + before_rollover.ToString());
 Console.WriteLine("after_rollover: " + after_rollover.ToString());
 Console.WriteLine("duration: " + duration.ToString());

imprime correctement:

 before_rollover: 2147483642
 after_rollover: -2147483641
 duration: 13

Vous n'avez pas à vous soucier du bit de signe. C #, comme C, laisse le CPU gérer cela.

C'est une situation courante que j'ai rencontrée auparavant avec le décompte du temps dans les systèmes embarqués. Je ne comparerais jamais beforerollover <afterrollover directement, par exemple. Je toujours effectuerais la soustraction pour trouver la durée qui tient compte du roulement, puis baserais tous les autres calculs sur la durée.

24
Matthew

Vous voulez probablement System.Diagnostics.StopWatch .

10
Joel Coehoorn

Si vous recherchez la fonctionnalité de Environment.TickCount Mais sans la surcharge de création de nouveaux objets Stopwatch, vous pouvez utiliser la méthode statique Stopwatch.GetTimestamp() (avec Stopwatch.Frequency) pour calculer de longues périodes. Parce que GetTimestamp() renvoie un long, il ne débordera pas pendant très, très longtemps (plus de 100 000 ans, sur la machine que j'utilise pour écrire ceci). Il est également beaucoup plus précis que Environment.TickCount Qui a une résolution maximale de 10 à 16 millisecondes.

9
Mark

Utilisation

System.Diagnostics.Stopwatch

Il a une propriété appelée

EllapsedMilliseconds
8
Martin Kool

Environment.TickCount semble être beaucoup plus rapide que les autres solutions:

Environment.TickCount 71
DateTime.UtcNow.Ticks 213
sw.ElapsedMilliseconds 1273

Les mesures ont été générées par le code suivant:

static void Main( string[] args ) {
    const int max = 10000000;
    //
    //
    for ( int j = 0; j < 3; j++ ) {
        var sw = new Stopwatch();
        sw.Start();
        for ( int i = 0; i < max; i++ ) {
            var a = Environment.TickCount;
        }
        sw.Stop();
        Console.WriteLine( $"Environment.TickCount {sw.ElapsedMilliseconds}" );
        //
        //
        sw = new Stopwatch();
        sw.Start();
        for ( int i = 0; i < max; i++ ) {
            var a = DateTime.UtcNow.Ticks;
        }
        sw.Stop();
        Console.WriteLine( $"DateTime.UtcNow.Ticks {sw.ElapsedMilliseconds}" );
        //
        //
        sw = new Stopwatch();
        sw.Start();
        for ( int i = 0; i < max; i++ ) {
            var a = sw.ElapsedMilliseconds;
        }
        sw.Stop();
        Console.WriteLine( $"sw.ElapsedMilliseconds {sw.ElapsedMilliseconds}" );
    }
    Console.WriteLine( "Done" );
    Console.ReadKey();
}
6
cskwg

Voici une sorte de résumé actualisé mis à jour de ce qui peut être les réponses les plus utiles commentaires dans ce fil + benchmarks supplémentaires et variantes:

Première chose d'abord: comme d'autres l'ont souligné dans les commentaires, les choses ont changé au cours des dernières années et avec Windows "moderne" (Win XP ++) et .NET, et le matériel moderne il n'y a pas ou petites raisons de ne pas utiliser Chronomètre (). Voir MSDN pour plus de détails. Citations:

"La précision QPC est-elle affectée par les changements de fréquence du processeur causés par la gestion de l'alimentation ou la technologie Turbo Boost?
Non. Si le processeur a un TSC invariant, le QPC n'est pas affecté par ce type des changements. Si le processeur n'a pas de TSC invariant, QPC reviendra à un minuteur matériel de plate-forme qui ne sera pas affecté par les changements de fréquence du processeur ou la technologie Turbo Boost.

QPC fonctionne-t-il de manière fiable sur les systèmes multiprocesseurs, les systèmes multicœurs et les systèmes avec hyper-threading?
Oui

Comment puis-je déterminer et valider que QPC fonctionne sur ma machine?
Vous n'avez pas besoin d'effectuer de telles vérifications.

Quels processeurs ont des TSC non invariants? [..Lire plus loin ..] "

Mais si vous n'avez pas besoin de la précision de Stopwatch () ou si vous voulez au moins connaître exactement les performances de Stopwatch (statique par rapport à l'instance) et d'autres variantes possibles, continuez à lire:

J'ai repris le benchmark ci-dessus de cskwg et étendu le code pour plus de variantes. J'ai mesuré avec un i7 4700 MQ et C # 7 vieux de quelques années avec VS 2017 (pour être plus précis, compilé avec .NET 4.5.2, malgré les littéraux binaires, il s'agit de C # 6 (utilisé comme ceci: les littéraux de chaîne et 'en utilisant statique En particulier, les performances du chronomètre () semblent être améliorées par rapport à l'indice de référence mentionné.

Ceci est un exemple de résultats de 10 millions de répétitions dans une boucle, comme toujours, les valeurs absolues ne sont pas importantes, mais même les valeurs relatives peuvent différer sur d'autres matériels:

32 bits, mode Release sans optimisation:

Mesuré: GetTickCount64 () [ms]: 275
Mesuré: Environment.TickCount [ms]: 45
Mesuré: DateTime.UtcNow.Ticks [ms]: 167
Mesuré: Chronomètre: .ElapsedTicks [ms]: 277
Mesuré: Chronomètre: .ElapsedMilliseconds [ms]: 548
Mesuré: Chronomètre statique.GetTimestamp [ms]: 193
Mesuré: Chronomètre + conversion en DateTime [ms]: 551
Comparez cela avec DateTime.Now.Ticks [ms]: 9010

32 bits, mode Release, optimisé:

Mesuré: GetTickCount64 () [ms]: 198
Mesuré: Environment.TickCount [ms]: 39
Mesuré: DateTime.UtcNow.Ticks [ms]: 66 (!)
Mesuré: Chronomètre: .ElapsedTicks [ms]: 175
Mesuré: Chronomètre: .ElapsedMilliseconds [ms]: 491
Mesuré: Chronomètre statique.GetTimestamp [ms]: 175
Mesuré: Chronomètre + conversion en DateTime [ms]: 510
Comparez cela avec DateTime.Now.Ticks [ms]: 8460

64 bits, mode Release sans optimisation:

Mesuré: GetTickCount64 () [ms]: 205
Mesuré: Environment.TickCount [ms]: 39
Mesuré: DateTime.UtcNow.Ticks [ms]: 127
Mesuré: Chronomètre: .ElapsedTicks [ms]: 209
Mesuré: Chronomètre: .ElapsedMilliseconds [ms]: 285
Mesuré: Chronomètre statique.GetTimestamp [ms]: 187
Mesuré: Chronomètre + conversion en DateTime [ms]: 319
Comparez cela avec DateTime.Now.Ticks [ms]: 3040

64 bits, mode Release, optimisé:

Mesuré: GetTickCount64 () [ms]: 148
Mesuré: Environment.TickCount [ms]: 31 (vaut-il toujours la peine?)
Mesuré: DateTime.UtcNow.Ticks [ms]: 76 (!)
Mesuré: Chronomètre: .ElapsedTicks [ms]: 178
Mesuré: Chronomètre: .ElapsedMilliseconds [ms]: 226
Mesuré: Chronomètre statique.GetTimestamp [ms]: 175
Mesuré: Chronomètre + conversion en DateTime [ms]: 246
Comparez cela avec DateTime.Now.Ticks [ms]: 3020

Il peut être très intéressant de noter que la création d'une valeur DateTime pour imprimer l'heure du chronomètre semble avoir presque aucun coût. Intéressant d'une manière plus académique que pratique, le chronomètre statique est légèrement plus rapide (comme prévu). Certains points d'optimisation sont assez intéressants. Par exemple, je ne peux pas expliquer pourquoi Stopwatch.ElapsedMilliseconds uniquement avec 32 bits est si lent par rapport à ses autres variantes, par exemple la statique. Cela et DateTime.Maintenant plus que doubler leur vitesse avec 64 bits.

Vous pouvez voir: Seulement pour des millions d'exécutions, le temps du chronomètre commence à avoir de l'importance. Si c'est vraiment le cas (mais attention à la micro-optimisation trop précoce), il peut être intéressant qu'avec GetTickCount64 (), mais surtout avec DateTime.UtcNow , vous avez une minuterie 64 bits (longue) avec moins de précision que le chronomètre, mais plus rapide, afin que vous n'ayez pas à vous soucier de l'environnement 32 bits "laid". TickCount.

Comme prévu, DateTime.Now est de loin le plus lent de tous.

Si vous l'exécutez, le code récupère également votre précision actuelle du chronomètre et plus encore.

Voici le code de référence complet:

using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using static System.Environment;

[...]

    [DllImport("kernel32.dll") ]
    public static extern UInt64 GetTickCount64(); // Retrieves a 64bit value containing ticks since system start

    static void Main(string[] args)
    {
        const int max = 10_000_000;
        const int n = 3;
        Stopwatch sw;

        // Following Process&Thread lines according to tips by Thomas Maierhofer: https://codeproject.com/KB/testing/stopwatch-measure-precise.aspx
        // But this somewhat contradicts to assertions by MS in: https://msdn.Microsoft.com/en-us/library/windows/desktop/dn553408%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396#Does_QPC_reliably_work_on_multi-processor_systems__multi-core_system__and_________systems_with_hyper-threading
        Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(1); // Use only the first core
        Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;
        Thread.CurrentThread.Priority = ThreadPriority.Highest;
        Thread.Sleep(2); // warmup

        Console.WriteLine($"Repeating measurement {n} times in loop of {max:N0}:{NewLine}");
        for (int j = 0; j < n; j++)
        {
            sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < max; i++)
            {
                var tickCount = GetTickCount64();
            }
            sw.Stop();
            Console.WriteLine($"Measured: GetTickCount64() [ms]: {sw.ElapsedMilliseconds}");
            //
            //
            sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < max; i++)
            {
                var tickCount = Environment.TickCount; // only int capacity, enough for a bit more than 24 days
            }
            sw.Stop();
            Console.WriteLine($"Measured: Environment.TickCount [ms]: {sw.ElapsedMilliseconds}");
            //
            //
            sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < max; i++)
            {
                var a = DateTime.UtcNow.Ticks;
            }
            sw.Stop();
            Console.WriteLine($"Measured: DateTime.UtcNow.Ticks [ms]: {sw.ElapsedMilliseconds}");
            //
            //
            sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < max; i++)
            {
                var a = sw.ElapsedMilliseconds;
            }
            sw.Stop();
            Console.WriteLine($"Measured: Stopwatch: .ElapsedMilliseconds [ms]: {sw.ElapsedMilliseconds}");
            //
            //
            sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < max; i++)
            {
                var a = Stopwatch.GetTimestamp();
            }
            sw.Stop();
            Console.WriteLine($"Measured: static Stopwatch.GetTimestamp [ms]: {sw.ElapsedMilliseconds}");
            //
            //
            DateTime dt=DateTime.MinValue; // just init
            sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < max; i++)
            {
                var a = new DateTime(sw.Elapsed.Ticks); // using variable dt here seems to make nearly no difference
            }
            sw.Stop();
            //Console.WriteLine($"Measured: Stopwatch+conversion to DateTime [s] with millisecs: {dt:s.fff}");
            Console.WriteLine($"Measured: Stopwatch+conversion to DateTime [ms]:  {sw.ElapsedMilliseconds}");

            Console.WriteLine();
        }
        //
        //
        sw = new Stopwatch();
        var tickCounterStart = Environment.TickCount;
        sw.Start();
        for (int i = 0; i < max/10; i++)
        {
            var a = DateTime.Now.Ticks;
        }
        sw.Stop();
        var tickCounter = Environment.TickCount - tickCounterStart;
        Console.WriteLine($"Compare that with DateTime.Now.Ticks [ms]: {sw.ElapsedMilliseconds*10}");

        Console.WriteLine($"{NewLine}General Stopwatch information:");
        if (Stopwatch.IsHighResolution)
            Console.WriteLine("- Using high-resolution performance counter for Stopwatch class.");
        else
            Console.WriteLine("- Using high-resolution performance counter for Stopwatch class.");

        double freq = (double)Stopwatch.Frequency;
        double ticksPerMicroSec = freq / (1000d*1000d) ; // microsecond resolution: 1 million ticks per sec
        Console.WriteLine($"- Stopwatch accuracy- ticks per microsecond (1000 ms): {ticksPerMicroSec:N1}");
        Console.WriteLine(" (Max. tick resolution normally is 100 nanoseconds, this is 10 ticks/microsecond.)");

        DateTime maxTimeForTickCountInteger= new DateTime(Int32.MaxValue*10_000L);  // tickCount means millisec -> there are 10.000 milliseconds in 100 nanoseconds, which is the tick resolution in .NET, e.g. used for TimeSpan
        Console.WriteLine($"- Approximated capacity (maxtime) of TickCount [dd:hh:mm:ss] {maxTimeForTickCountInteger:dd:HH:mm:ss}");
        // this conversion from seems not really accurate, it will be between 24-25 days.
        Console.WriteLine($"{NewLine}Done.");

        while (Console.KeyAvailable)
            Console.ReadKey(false);
        Console.ReadKey();
    }
6
Philm

J'utilise Environment.TickCount parce que:

  1. La classe Chronomètre n'est pas dans le Compact Framework.
  2. Le chronomètre utilise le même mécanisme de synchronisation sous-jacent que TickCount, de sorte que les résultats ne seront plus ou moins précis.
  3. Le problème de bouclage avec TickCount est cosmiquement peu susceptible d'être touché (vous devez laisser votre ordinateur en marche pendant 27 jours, puis essayer de mesurer un temps qui arrive juste à s'étendre sur le moment de bouclage ), et même si vous le frappiez, le résultat serait un énorme intervalle de temps négatif (il se démarquerait donc).

Cela étant dit, je recommanderais également d'utiliser le chronomètre, s'il est à votre disposition. Ou vous pouvez prendre environ 1 minute et écrire une classe de type chronomètre qui enveloppe Environment.TickCount.

BTW, je ne vois rien dans la documentation du chronomètre qui mentionne le problème de bouclage avec le mécanisme de minuterie sous-jacent, donc je ne serais pas du tout surpris de constater que le chronomètre souffre du même problème. Mais encore une fois, je ne passerais pas de temps à m'en inquiéter.

0
MusiGenesis

J'allais dire envelopper dans une classe chronomètre, mais Grzenio a déjà dit la bonne chose, donc je vais lui donner une légère augmentation. Une telle encapsulation exclut la décision de savoir quelle voie est la meilleure, et cela peut changer avec le temps. Je me souviens d'avoir été choqué de voir à quel point il peut être coûteux de gagner du temps sur certains systèmes, il est donc très important d'avoir un endroit qui puisse mettre en œuvre la meilleure technique.

0
dongilmore

Pour un chronométrage en une seule fois, il est encore plus simple d'écrire

Stopwatch stopWatch = Stopwatch.StartNew();
...dostuff...
Debug.WriteLine(String.Format("It took {0} milliseconds",
                              stopWatch.EllapsedMilliseconds)));

Je suppose que le wraparound cosmiquement improbable dans TickCount est encore moins préoccupant pour StopWatch, étant donné que le champ ElapsedTicks est long. Sur ma machine, StopWatch est en haute résolution, à 2,4e9 ticks par seconde. Même à ce rythme, il faudrait plus de 121 ans pour déborder le champ des tiques. Bien sûr, je ne sais pas ce qui se passe sous les couvertures, alors prenez ça avec un grain de sel. Cependant, je remarque que la documentation de StopWatch ne mentionne même pas le problème de bouclage, contrairement à la doc pour TickCount.

0
vanmelle

Vous devez utiliser la classe Stopwatch à la place.

0
Igal Tabachnik