69 votes

Pourquoi les timers .NET sont-ils limités à une résolution de 15 ms?

Notez que je parle de quelque chose qui va appeler une fonction de rappel plus souvent qu'une fois tous les 15 ms en utilisant quelque chose comme System.Threading.Timer. Je ne suis pas demandant comment avec précision le temps d'un morceau de code à l'aide de quelque chose comme System.Diagnostics.Stopwatch ou même QueryPerformanceCounter.

Aussi, j'ai lu les questions connexes:

http://stackoverflow.com/questions/448761/accurate-windows-timer-system-timers-timer-is-limited-to-15-msec

http://stackoverflow.com/questions/163022/high-resolution-timer-in-net

Ni de qui fournit une réponse utile à ma question.

En outre, il est recommandé d'article MSDN, de mettre en Œuvre une Permanence de la mise à Jour, de Temps à Haute Résolution Fournisseur pour Windows, est une question de timing des choses plutôt que de fournir un flux continu de tiques.

Avec qui dit. . .

Il ya tout un tas de mauvaises informations là-bas sur le .NET minuterie objets. Par exemple, System.Timers.Timer est présenté comme "une haute performance de la minuterie optimisé pour les applications serveur." Et System.Threading.Timer est en quelque sorte considéré comme un citoyen de deuxième classe. La sagesse conventionnelle est que l' System.Threading.Timer est un wrapper autour des Fenêtres de la Minuterie de la File d'attente des Minuteries et qu' System.Timers.Timer c'est autre chose.

La réalité est très différente. System.Timers.Timer est juste une mince composant wrapper autour de System.Threading.Timer (il suffit d'utiliser un Réflecteur ou ILDASM à coup d'oeil à l'intérieur d' System.Timers.Timer et vous verrez la référence à l' System.Threading.Timer), et a peu de code qui permettra de fournir du fil automatique de la synchronisation de sorte que vous n'avez pas à le faire.

System.Threading.Timer, comme il s'avère , n'est pas un wrapper pour le compte à rebours de la File d'attente des Chronomètres. Au moins pas dans le runtime 2.0, qui a été utilisé .NET 2.0 par le biais de .NET 3.5. Quelques minutes avec le partage de la Source de la CLI montre que l'exécution implémente son propre minuterie file d'attente qui est similaire à la File d'attente du Minuteur Minuteries, mais jamais réellement appelle les fonctions Win32.

Il semble que l' .NET 4.0 runtime met également en œuvre sa propre file d'attente de minuterie. Mon programme de test (voir ci-dessous) fournit des résultats similaires .NET 4.0 comme il le fait en vertu de l' .NET 3.5. J'ai créé ma propre gestion de l'enveloppe pour la Minuterie de la File d'attente des Minuteries et prouvé que je peux obtenir de 1 ms (avec une assez bonne précision), donc, je considère qu'il est peu probable que je suis en train de lire la CLI source de mal.

J'ai deux questions:

Tout d'abord, quelles sont les causes de l'exécution de la mise en œuvre de la minuterie de la file d'attente pour être si lent? Je ne peux pas faire mieux que de 15 ms résolution et la précision semble être de l'ordre de -1 à +30 ms. C'est, si je demande 24 ms, je vais chercher les tiques n'importe où à partir de 23 à 54 ms d'intervalle. Je suppose que je pourrais passer plus de temps avec la CLI de la source de la trace de la réponse, mais pensé que quelqu'un ici pourrait savoir.

Deuxièmement, et je me rends compte que c'est plus difficile de répondre, pourquoi ne pas utiliser la Minuterie de la File d'attente des Chronomètres? Je me rends compte que .NET 1.x avait à exécuter sur Win9x, qui n'ont pas de ces Api, mais ils ont existé depuis Windows 2000, qui, si je me souviens bien, est l'exigence minimale .NET 2.0. Est-ce parce que la CLI a eu à courir sur la non-Windows boîtes?

Mon minuteries programme de test:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;

namespace TimerTest
{
    class Program
    {
        const int TickFrequency = 5;
        const int TestDuration = 15000;   // 15 seconds

        static void Main(string[] args)
        {
            // Create a list to hold the tick times
            // The list is pre-allocated to prevent list resizing
            // from slowing down the test.
            List<double> tickTimes = new List<double>(2 * TestDuration / TickFrequency);

            // Start a stopwatch so we can keep track of how long this takes.
            Stopwatch Elapsed = Stopwatch.StartNew();

            // Create a timer that saves the elapsed time at each tick
            Timer ticker = new Timer((s) =>
                {
                    tickTimes.Add(Elapsed.ElapsedMilliseconds);
                }, null, 0, TickFrequency);

            // Wait for the test to complete
            Thread.Sleep(TestDuration);

            // Destroy the timer and stop the stopwatch
            ticker.Dispose();
            Elapsed.Stop();

            // Now let's analyze the results
            Console.WriteLine("{0:N0} ticks in {1:N0} milliseconds", tickTimes.Count, Elapsed.ElapsedMilliseconds);
            Console.WriteLine("Average tick frequency = {0:N2} ms", (double)Elapsed.ElapsedMilliseconds / tickTimes.Count);

            // Compute min and max deviation from requested frequency
            double minDiff = double.MaxValue;
            double maxDiff = double.MinValue;
            for (int i = 1; i < tickTimes.Count; ++i)
            {
                double diff = (tickTimes[i] - tickTimes[i - 1]) - TickFrequency;
                minDiff = Math.Min(diff, minDiff);
                maxDiff = Math.Max(diff, maxDiff);
            }

            Console.WriteLine("min diff = {0:N4} ms", minDiff);
            Console.WriteLine("max diff = {0:N4} ms", maxDiff);

            Console.WriteLine("Test complete.  Press Enter.");
            Console.ReadLine();
        }
    }
}

31voto

Arnold Spence Points 12759

Peut-être le document lié ici l'explique un peu. C'est un peu sec donc j'ai parcouru rapidement :)

Citant l'intro:

Le système détermine la résolution du timer quelle est la fréquence de Windows effectue deux actions principales:

  • Mise à jour le timer tick le comte si une tique s'est écoulé.
  • Vérifier si une prévue objet timer a expiré.

Un timer tick est une notion de écoulée le temps que Windows utilise pour suivre les l'heure de la journée et du fil quantique fois. Par défaut, l'interruption de l'horloge et timer tick sont la même, mais les Fenêtres ou une application peut modifier l'horloge interruption de la période.

La valeur par défaut de la minuterie résolution sur Windows 7 est de 15,6 millisecondes (ms). Certaines applications réduire à 1 ms, ce qui réduit le pour la batterie, sur des systèmes mobiles par autant que 25 pour cent.

Originaire de: Minuteries, la Résolution du Timer, et le Développement d'un Code Efficace (docx).

25voto

Arno Points 2226

La résolution du timer est donné par le système de pulsation. Ce sont généralement les valeurs par défaut de 64 battements/s qui est 15.625 mme. Cependant, il ya des façons de modifier ces paramètres au niveau du système pour atteindre la minuterie résolutions jusqu'à 1 ms ou même à 0,5 ms sur les nouvelles plates-formes:

1. Va pour 1 ms, par le moyen de la minuterie multimédia interface:

La minuterie multimédia interface est capable de fournir jusqu'à 1 ms résolution. Voir Sur le Multimédia Minuteries (MSDN), l'Obtention et le Réglage de la Résolution du Timer (MSDN), et cette réponse pour plus de détails sur timeBeginPeriod. Remarque: N'oubliez pas d'appeler la timeEndPeriod pour revenir à la valeur par défaut la résolution du timer.

Comment faire:

#define TARGET_RESOLUTION 1         // 1-millisecond target resolution

TIMECAPS tc;
UINT     wTimerRes;

if (timeGetDevCaps(&tc, sizeof(TIMECAPS)) != TIMERR_NOERROR) 
{
   // Error; application can't continue.
}

wTimerRes = min(max(tc.wPeriodMin, TARGET_RESOLUTION), tc.wPeriodMax);
timeBeginPeriod(wTimerRes); 

//       do your stuff here at approx. 1 ms timer resolution

timeEndPeriod(wTimerRes); 

Remarque: Cette procédure est disponible à d'autres procédés et la résolution obtenue s'applique à l'échelle du système. La plus haute resoltion demandée par n'importe quel processus sera active, l'esprit les conséquences.

2. Aller à 0,5 ms résolution:

Vous pouvez obtenir 0,5 ms résolution par le biais de l'caché API NtSetTimerResolution(). NtSetTimerResolution est exporté par le natif de Windows NT bibliothèque NTDLL.DLL. Voir Comment régler la minuterie, la résolution de 0,5 ms ? sur MSDN. Cependant, la véritable réalisables resoltion est déterminée par le matériel sous-jacent. Du matériel moderne, prend en charge 0,5 ms résolution. Encore plus de détails se trouvent dans l'Intérieur de Windows NT Minuteurs de Haute Résolution. Les résolutions prises en charge peuvent être obtenues par un appel à NtQueryTimerResolution().

Comment faire:

#define STATUS_SUCCESS 0
#define STATUS_TIMER_RESOLUTION_NOT_SET 0xC0000245

// after loading NtSetTimerResolution from ntdll.dll:

// The requested resolution in 100 ns units:
ULONG DesiredResolution = 5000;  
// Note: The supported resolutions can be obtained by a call to NtQueryTimerResolution()

ULONG CurrentResolution = 0;

// 1. Requesting a higher resolution
// Note: This call is similar to timeBeginPeriod.
// However, it to to specify the resolution in 100 ns units.
if (NtSetTimerResolution(DesiredResolution ,TRUE,&CurrentResolution) != STATUS_SUCCESS) {
    // The call has failed
}

printf("CurrentResolution [100 ns units]: %d\n",CurrentResolution);
// this will show 5000 on more modern platforms (0.5ms!)

//       do your stuff here at 0.5 ms timer resolution

// 2. Releasing the requested resolution
// Note: This call is similar to timeEndPeriod 
switch (NtSetTimerResolution(DesiredResolution ,FALSE,&CurrentResolution) {
    case STATUS_SUCCESS:
        printf("The current resolution has returned to %d [100 ns units]\n",CurrentResolution);
        break;
    case STATUS_TIMER_RESOLUTION_NOT_SET:
        printf("The requested resolution was not set\n");   
        // the resolution can only return to a previous value by means of FALSE 
        // when the current resolution was set by this application      
        break;
    default:
        // The call has failed

}

Remarque: La fonctionnalité de NtSetTImerResolution est fondamentalement mappés aux fonctions timeBeginPeriod et timeEndPeriod par l'aide de la valeur booléenne Set (voir à l'Intérieur de Windows NT Minuteurs de Haute Résolution pour plus de détails sur le programme et toutes ses implications). Cependant, la suite multimédia limites de la granularité en millisecondes et NtSetTimerResolution permet de définir le sous-ordre de la milliseconde valeurs.

-3voto

Jay Points 1457

C'est fiable jusqu'à 1 μs

Voir http://www.codeproject.com/Articles/571289/Obtaining-Microsecond-Precision-in-NET

Il fournit beaucoup moins que tout ce que vous avez vu auparavant et surpasse les compteurs de performance!

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X