Comment chronométrer mon programme ? Comment mesurer précisément le temps ?
IntroductionIl est parfois utile de mesurer une durée entre 2 instants lors de l'exécution du programme, par exemple pour réaliser un chronomètre, pour mesurer les performances d'une fonction ou d'une section de code, pour effectuer une opération à intervalle régulier ou après un temps donné, etc. En fonction de la précision souhaitée, plusieurs méthodes sont possibles. Cet article présente les méthodes suivantes : Le contrôle TimerCe contrôle permet de déclencher un évènement à intervalle régulier. Il est possible via sa propriété "Interval" de fixer la fréquence de déclenchement à 1 ms mais, en pratique, la précision n'est jamais supérieure à 55 ms dans le meilleur des cas. Sa bonne intégration dans VB et sa simplicité d'utilisation en font néanmoins un outil pratique pour des applications où une précision de l'ordre du dixième de seconde est suffisante. La fonction TimerA ne pas confondre avec la fonction "Time", la fonction Timer retourne un Single qui représente le nombre de secondes écoulées depuis minuit. La fonction retourne un nombre en simple précision, limité à 2 décimales. On obtient donc au mieux des centièmes de secondes. Dans la pratique, la précision est même souvent moins bonne. On peut néanmoins l'utiliser quand on n'a pas besoin d'une précision meilleure que le centième voire le dixième de seconde. Attention : il ne faut jamais utiliser la valeur telle quelle, mais toujours utiliser une différence entre 2 appels à cette fonction (à cause du passage à zéro à minuit). Exemple d'utilisation Dim t_start As Single, t_end As Single, elapsed As Single Dim i As Long, s As String t_start = Timer For i = 1 To 30000 s = s & "HELLO " Next i t_end = Timer elapsed = CSng(Round(t_end - t_start, 2)) MsgBox "Il faut " & elapsed & " pour faire 30000 concaténations" L'utilisation dans ce contexte est correcte, car la portion de code mesurée (la boucle de 30000 itérations) mets plus d'une seconde pour s'exécuter. Sur un grand nombre d'itérations, la précision obtenue est suffisante. La fonction (API) timeGetTimeCette fonction retourne le "system time" (durée depuis le dernier démarrage de Windows), en millisecondes. Note : la valeur retournée repasse à zéro environ tous les 49 jours. Pour des calculs de durée, on veillera à toujours utiliser non pas le retour de la fonction mais la différence entre les retours de 2 appels successifs (comme pour Timer). La précision par défaut peut varier, mais on peut utiliser les fonctions timeBeginPeriod et timeEndPeriod pour spécifier la précision. La meilleure précision qu'il est possible de spécifier est de 1 ms. En pratique, la résolution est de l'ordre de 16 millisecondes (ce qui signifie qu'il faut 16 millisecondes pour avoir un changement de valeur entre 2 appels successifs à timeGetTime). Exemple d'utilisationPrivate Declare Function timeGetTime Lib "winmm.dll" () As Integer Private Declare Function timeBeginPeriod Lib "winmm.dll" (ByVal uPeriod As Integer) As Integer Private Declare Function timeEndPeriod Lib "winmm.dll" (ByVal uPeriod As Integer) As Integer
Dim ret As Integer Dim t_start As Long, t_end As Long, elapsed As Long Dim i As Long, s As String
timeBeginPeriod 1 t_start = timeGetTime() For i = 1 To 30000 s = s & "HELLO " Next i t_end = timeGetTime() elapsed = t_end - t_start MsgBox "Il faut " & elapsed & " millisecondes pour faire 30000 concaténations" timeEndPeriod 1 Cette API offre de bonnes performances. Son utilisation est tout à fait adaptée pour toute application nécessitant une précision de l'ordre d'une dizaine de millisecondes. C'est l'API de choix pour effectuer des mesures de performances. On n'a de toute façon pas besoin d'une résolution supérieure, puisqu'une mesure de performances ne se conçoit que comme la moyenne des mesures d'un grand nombre d'exécutions de ce que l'on veut mesurer. La fonction (API) GetTickCountCette fonction est très proche de la fonction timeGetTime. Elle retourne aussi le nombre de millisecondes écoulées depuis le dernier démarrage du système. Sa résolution théorique est de 1 milliseconde, mais elle est limitée par la résolution du timer du système. Son comportement peut être affectée par des appels à l'API SetSystemTimeAdjustement. On se réfèrera à la documentation (voir section "Aller plus loin") pour plus de détails. En pratique, la résolution est de l'odre de 16 milliseconde, comme pout timeGetTime (ce qui signifie qu'il faut 16 millisecondes pour avoir un changement de valeur entre 2 appels successifs à GetTickCount). Exemple d'utilisationPrivate Declare Function GetTickCount Lib "kernel32" () As Long
Private Sub MySleep(ByVal sleep_interval As Long) Dim start_time As Long
start_time = GetTickCount While start_time + sleep_interval > GetTickCount DoEvents Wend End Sub
La fonction (API) QueryPerformanceCounter : timer à très haute résolutionL'API Windows met à la disposition des programmeurs un set d'API regroupées sous le nom de "high-resolution performance counter" (compteurs à haute résolution). Ces fonctions permettent de mesurer des durées ou intervalles de durées avec une très grande précision. La précision réelle finale dépend de la vitesse du processeur de la machine. Sur une machine récente à 3.4 Ghz, la précision théorique du compteur est de 1/3.391.640.000, ce qui donne une résolution de environ 0,3 nanoseconde... Sur une machine plus modeste à 1 Ghz, on atteint encore une résolution de 1 nanoseconde (1.E-9 seconde). A noter que l'exécution de cette fonction (l'appel en lui même) prend de une à deux microsecondes sur une machine à 3,4 Ghz. Note : la documentation indique que certains hardwares peuvent ne pas supporter cette API. Dans la pratique, nous n'avons jamais rencontré ce cas. Ils semblent être implémentés sur tous les processeurs plus récents qu'un 80386. Il faut signaler que peu d'applications nécessitent de connaitre ou de mesurer le temps avec une telle précision. L'utilisation de cet ensemble de fonctions est probablement à réserver pour des cas très particuliers, notamment d'interfaçage ou de contrôle avec du matériel ou des périphériques demandant un pilotage ou un monitoring ultra-précis. On pourra aussi l'utiliser pour réaliser des mesures de performances, en gardant à l'esprit que la mesure doit se faire sur un grand nombre d'exécutions. La manipulation de ces API n'est pas très compliquée, mais nécessite l'emploi de structures de données particulières pour le stockage précis de très grands nombres. Exemple d'utilisation : implémentation d'une Classe ChronomètreEnregistrer le code suivant dans une classe : chrono.cls Option Explicit
Private Type LARGE_INTEGER LowPart As Long HighPart As Long End Type
Private Declare Function QueryPerformanceCounter Lib "kernel32" (lpPerformanceCount As LARGE_INTEGER) As Long Private Declare Function QueryPerformanceFrequency Lib "kernel32" (lpFrequency As LARGE_INTEGER) As Long Private Declare Sub CopyMemory Lib "kernel32.dll" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
Private liStart As LARGE_INTEGER Private liStop As LARGE_INTEGER Private liFrequency As LARGE_INTEGER
Public Enum CounterUnit Second = 1 Millisecond = 2 microsecond = 3 nanosecond = 4 End Enum
Private m_ResultUnit As CounterUnit
Public Property Let ResultUnit(ur As CounterUnit) m_ResultUnit = ur End Property
Public Property Get ResultUnit() As CounterUnit
ResultUnit = m_ResultUnit End Property
Public Sub TimerStart()
QueryPerformanceCounter liStart End Sub
Public Sub TimerStop()
QueryPerformanceCounter liStop End Sub
Public Function getTimeElapsed() As Double Dim cuStart As Currency Dim cuStop As Currency Dim cuFreq As Currency Dim v As Double QueryPerformanceFrequency liFrequency cuStart = LargeIntToCurrency(liStart) cuStop = LargeIntToCurrency(liStop) cuFreq = LargeIntToCurrency(liFrequency) v = CDbl(cuStop - cuStart) / CDbl(cuFreq) Select Case ResultUnit Case Second getTimeElapsed = v Case Millisecond getTimeElapsed = v * 1000# Case microsecond getTimeElapsed = v * 1000000# Case nanosecond getTimeElapsed = v * 1000000000# End Select End Function
Private Function LargeIntToCurrency(liInput As LARGE_INTEGER) As Currency
CopyMemory LargeIntToCurrency, liInput, LenB(liInput) LargeIntToCurrency = LargeIntToCurrency * 10000 End Function Et pour l'utiliser, rien de plus simple : Dim c As New chrono Dim i As Integer, s As String
c.TimerStart For i = 1 To 30000 s = s & "HELLO " Next i c.TimerStop c.ResultUnit = microsecond MsgBox "Temps écoulé : " & c.getTimeElapsed & " microsecondes." A noter qu'on peut aussi utiliser directement le type Currency au lieu de la structure LARGE_INTEGER : Private Declare Function QueryPerformanceCounter Lib "Kernel32" (X As Currency) As Boolean Private Declare Function QueryPerformanceFrequency Lib "Kernel32" (X As Currency) As Boolean Sub Test() Dim Ctr1 As Currency, Ctr2 As Currency, Freq As Currency Dim i As Long, n As Double QueryPerformanceCounter Ctr1 For i = 1 To 10000 n = Sqr(2) Next i QueryPerformanceCounter Ctr2 QueryPerformanceFrequency Freq MsgBox "Temps écoulé : " & ((Ctr2 - Ctr1) / Freq) * 1000 & " millisecondes" End Sub Pour aller plus loin Voir aussi : |