Question

Puis-je utiliser Environment.TickCount pour calculer des durées de temps?

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

Étant donné que TickCount est signé et qu'il basculera au bout de 25 jours (il faut 50 jours pour atteindre les 32 bits, mais vous devez supprimer le bit signé pour donner un sens au calcul), il semble que trop risqué pour être utile.

J'utilise plutôt DateTime.Now. Est-ce la meilleure façon de faire cela?

DateTime start = DateTime.Now;
// Do stuff
TimeSpan duration = DateTime.Now - start;
Console.WriteLine("That took " + duration.TotalMilliseconds + " ms");
Était-ce utile?

La solution

Utilisez la classe Stopwatch. Il existe un exemple correct sur msdn: http://msdn.microsoft .com / fr-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;

Autres conseils

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

Remarque: La valeur renvoyée est Int32. Ce compteur affiche donc chaque période de ~ 49,7 jours. Vous ne devriez pas l'utiliser pour mesurer des intervalles aussi longs.

DateTime.Ticks est basé sur GetSystemTimeAsFileTime () WinAPI une fonction. C'est en 100 nanosecondes (dixièmes de microsecondes). La précision réelle de DateTime.Ticks dépend du système. Sous XP, l’incrément de l’horloge système est d’environ 15,6 ms, comme dans Environment.TickCount. Sous Windows 7, la précision est de 1 ms (tandis que la taille de Environemnt.TickCount est toujours de 15,6 ms). Toutefois, si un mode d'économie d'énergie est utilisé (généralement sur les ordinateurs portables), il peut également atteindre 15,6 ms.

Le chronomètre est basé sur la QueryPerformanceCounter () , fonction WinAPI ( mais si votre système ne prend pas en charge le compteur de performance haute résolution, DateTime.Ticks est utilisé)

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

  • cela peut ne pas être fiable sur les systèmes multiprocesseurs (voir MS kb895980 , kb896256 )
  • cela peut ne pas être fiable si la fréquence du processeur varie (lisez cet 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();
}

Pourquoi êtes-vous inquiet au sujet 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 depuis combien de temps le système fonctionne, tant que vous ne vous préoccupez que de la partie de votre temps d'exécution (au lieu d'effectuer directement des comparaisons inférieures ou supérieures aux points de départ et d'arrivée). C'est à dire. ceci:

 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 processeur le gérer.

C’est une situation courante que j’ai rencontrée auparavant avec des comptages de temps dans les systèmes embarqués. Je ne comparerais jamais beforerollover & Lt; afterrollover directement, par exemple. J'effectuerais toujours la soustraction pour trouver la durée qui prend en compte le survol, puis baserais tout autre calcul sur la durée.

Vous voulez probablement System.Diagnostics.StopWatch . / p>

Si vous recherchez la fonctionnalité de Environment.TickCount mais sans la charge supplémentaire liée à la création de nouveaux Stopwatch objets, vous pouvez utiliser la méthode statique Stopwatch.GetTimestamp() (ainsi que Stopwatch.Frequency) pour calculer des intervalles de temps longs. Parce que GetTimestamp() renvoie un long, il ne débordera pas avant très, très longtemps (plus de 100 000 ans, sur la machine que j'utilise pour l'écrire). C'est aussi beaucoup plus précis que <=> qui a une résolution maximale de 10 à 16 millisecondes.

Utiliser

System.Diagnostics.Stopwatch

Il possède une propriété appelée

EllapsedMilliseconds

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

Voici en quelque sorte un & résumé actualisé de ce qui peut être les réponses les plus utiles & amp; commentaires dans cette discussion + points de repère et variantes supplémentaires:

Tout d’abord: comme d’autres l'ont souligné dans des commentaires, les choses ont changé ces dernières années et avec & "moderne &"; Windows (Win XP ++) et .NET, et le matériel moderne, il n'y a aucune ou peu de raisons de ne pas utiliser Stopwatch (). 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 dispose d'un TSC invariant , le QPC n'est pas affecté par ce type de modifications. Si le processeur ne dispose pas d'un TSC invariant, QPC rétablira un temporisateur 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 tels contrôles.

     

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 ou basé sur une instance) et d'autres variantes possibles, continuez à lire:

J'ai repris le repère ci-dessus de cskwg et étendu le code pour plus de variantes. J'ai mesuré avec un i7 4700 MQ et un 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 du C # 6 (utilisé de cette façon: littéraux de chaîne et utilisant statiques). En particulier, les performances de Stopwatch () semblent être améliorées par rapport au repère mentionné.

Voici 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 de libération sans optimisation:

  

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

.

32 bits, mode de libération, optimisé:

  

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

.

64 bits, mode de libération sans optimisation:

  

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

64 bits, mode de libération,optimisé:

  

Measured: GetTickCount64 () [ms]: 148
  Mesuré: Environment.TickCount [ms]: 31 (en vaut-il toujours la peine?)
  Mesuré: DateTime.UtcNow.Ticks [ms]: 76 (!)
  Mesuré: Chronomètre: .ElapsedTicks [ms]: 178
  Mesuré: Chronomètre: .ElapsedMilliseconds [ms]: 226
  Mesuré: statique Stopwatch.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 que la création d’une valeur DateTime pour imprimer l’heure du chronomètre ne semble avoir pratiquement aucun coût . De manière plus académique que pratique, il est intéressant de noter que 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 avec 32 bits est si lent comparé à ses autres variantes, par exemple statique. Ceci et DateTime.Now plus que doubler leur vitesse avec 64 bits.

Vous pouvez voir: ce n’est que pour des millions d’exécutions que l’heure du chronomètre commence à compter. Si tel est vraiment le cas (mais méfiez-vous de la micro-optimisation trop tôt), il peut être intéressant de noter qu'avec GetTickCount64 (), mais surtout avec DateTime.UtcNow , vous disposez d'un minuteur de 64 bits (long) avec moins de précision que Chronomètre, mais plus rapide, de sorte que vous n'avez pas à perdre votre temps avec le & "moche &" de 32 bits Environment.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 la précision actuelle de votre 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();
    }

Vous devez utiliser le chronomètre . classe à la place.

J'utilise Environment.TickCount car:

  1. La classe Chronomètre n'est pas dans Compact Framework.
  2. Chronomètre utilise le même mécanisme de synchronisation sous-jacent que TickCount, afin que les résultats ne soient ni plus ni moins précis.
  3. Le problème récurrent lié à TickCount ne risque pas d’être touché cosmiquement (vous devez laisser votre ordinateur en marche pendant 27 jours, puis essayer de mesurer une durée qui se situe juste au-dessus de la cadence. moment), et même si vous y parveniez, le résultat serait un énorme laps de temps négatif (donc cela se démarquerait).

Cela étant dit, je vous recommanderais également d'utiliser Stopwatch, s'il est disponible. Vous pouvez également prendre environ une minute et écrire une classe de type chronomètre qui englobe Environment.TickCount.

BTW, je ne vois rien dans la documentation de Stopwatch qui mentionne le problème récurrent du mécanisme de minuterie sous-jacent, donc je ne serais pas du tout surpris de constater que Stopwatch souffre du même problème. Mais encore une fois, je ne me soucierais pas de ça.

J'allais dire envelopper dans une classe de chronomètre, mais Grzenio a déjà dit la bonne chose, alors je vais lui donner une petite hausse. Une telle encapsulation détermine la meilleure façon de procéder et cela peut changer avec le temps. Je me souviens avoir été choqué de voir combien il peut être coûteux de perdre du temps sur certains systèmes. Il peut donc être très important d’avoir un site capable de mettre en œuvre la meilleure technique.

Pour un chronométrage ponctuel, il est encore plus simple d'écrire

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

J'imagine que l’improbable enveloppement de TickCount est encore moins inquiétant pour StopWatch, étant donné que le champ ElapsedTicks est long. Sur ma machine, StopWatch est haute résolution, à 2,4e9 ticks par seconde. Même à ce rythme, il faudrait plus de 121 ans pour déborder du champ des ticks. Bien sûr, je ne sais pas ce qui se passe sous les couvertures, alors prenez cela avec un grain de sel. Cependant, je remarque que la documentation de StopWatch ne mentionne même pas le problème enveloppant, contrairement au document de TickCount.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top