cGetTimer
Una clase para calcular periodos pequeños de tiempo

 

Publicado: el 31/Ago/1998
Actualizado el 19/Jul/2006 (ant. 09/Jul/2003)

Nota del 19/Jul/2006: Si quieres ver cómo usar una clase parecida para Visual Basic 2005 y C# 2.0, sigue este link.


La utilidad de esta clase es para hacer cálculos del tiempo que tarda un proceso en realizarse, por ejemplo para calcular lo que tarda un bucle, etc.
Debido a que la precisión del temporizador de Visual Basic no es demasiado buena, se usan funciones del API para calcular esos tiempos.
Inicialmente la hice con GetTickCount, pero después "me enteré" (viendo la ayuda del API de Windows), que hay otra función con más precisión aún... es la función timeGetTime, que usa lo que llaman el temporizador multimedia.
La precisión de esta función llega a ser de UN milisegundo, frente a los 55 milisegundos de GetTickCount, al menos en Windows 95 (y parece ser que también en Windows 98)

El concepto de la clase no es nuevo, de hecho lo he visto implementado de forma más o menos parecida en varios sitios y por diferentes autores, a los que agradezco que me resultara fácil crearla... lo que no pueda hacer un Copy & Paste y un par de modificaciones... je, je.
Tampoco es nada del otro mundo, lo que se necesita es un par de métodos:
Uno que Inicie el temporizador y otro que lo termine. Después alguna forma de saber cuánto ha sido ese tiempo y asunto arreglado.

En la clase que te muestro, se puede obtener el tiempo transcurrido de dos formas, (en dos funciones expuestas por la clase), una de ellas devuelve una cadena formateada con el tiempo transcurrido que además se encarga de detener el temporizador y que permite indicarle, mediante un parámetro, si el resultado debe mostrarlo usando milisegundos, segundos, etc. Realmente lo que hace es dividir el tiempo total por el valor del intervalo que le indicamos; por defecto lo muestra en segundos.
La otra forma de saber el tiempo transcurrido es obteniendo los milisegundos transcurridos, en esta función no se detiene el temporizador ni se formatea el resultado, el valor se devuelve como un LONG.

También se le puede indicar que función del API queremos usar para los cálculos, ésta se hace mediante la propiedad TimerType, que puede aceptar dos valores, según la función que queramos utilizar y que está enumerado para que sea fácil de indicar, ya que el nombre de los valores coinciden con los nombres de las funciones.

Pero veamos el código de la clase, que está más o menos comentado y que no te dará quebraderos de cabeza para entenderlo.

Nota del 09/Jul/2003:
Este es el código modificado con fecha 09/Jul/2003, prácticamente es el mismo que el usado en el año 98, pero con algunos cambios y añadidos para poder usar las clases mejoradas en Windows NT/2000/XP.

 

Pulsa en este link para bajarte el proyecto de prueba. (tGetTimer.zip 6.48 KB)
En este ejemplo también hay un control de usuario que sirve para mostrar líneas de separación con aspecto 3D.

Nota:
En este fichero se incluye ahora la nueva clase modificada con fecha 09/Jul/2003.
Además he modificado el proyecto ya que referenciaba incorrectamente a la clase y el control.

 

'------------------------------------------------------------------------------
' cGetTimer                                                         (16/Jun/98)
' Clase para calcular el tiempo entre eventos, etc.
'
' Ampliada para usar distintas funciones del API                    (29/Ago/98)
' Nuevos métodos/propiedades usando APIs especiales para NT/2000    (27/Oct/00)
' Comprobado en Windows XP Professional                             (09/Jul/03)
'
' Para usarla:
'   StartTimer          Iniciar el temporizador
'   StopTimer           Finalizar la cuenta
'   ElapsedTimer        Mostrar el tiempo transcurrido, formateado
'                       --- Nuevas propiedades ---
'   TimerType           El temporizador a usar
'   TotalElapsedTime    El número de milisegundos transcurridos
'
'   TimerPerformanceFrecuency
'   TimerPerformanceCounter
'
' ©Guillermo 'guille' Som, 1998-2003
'
' En esta clase se usa el tiempo del sistema, es decir el número de
' milisegundos transcurridos desde que se inició Windows.
' Este temporizador vuelve a cero una vez transcurridos 49.7 días,
' (2^32 milisegundos),
' por tanto el cálculo del tiempo transcurrido puede no ser exacto,
' dependiendo de cuándo se inició y terminó la cuenta.
'------------------------------------------------------------------------------
Option Explicit

'
'------------------------------------------------------------------------------
' Se puede usar timeGetTime si se quiere mayor precisión,
' ya que esta función tiene una precisión de:
' 1 milisegundo en Windows 95, y de
' 5 milisegundos o más, (es configurable), en Windows NT
'
Private Declare Function timeGetTime Lib "winmm.dll" () As Long
'
' Sólo para Windows NT/2000/XP
' Para averiguar si se dispone de temporizador de alta precisión
' y configurar la precisión de timeGetTime()
'
' Nota: El valor asignado a los parámetros Currency hay que multiplicarlo por 10000
Private Declare Function QueryPerformanceCounter Lib "kernel32" _
    (ByRef lpPerformanceCount As Currency) As Long
Private Declare Function QueryPerformanceFrequency Lib "kernel32" _
    (ByRef lpFrequency As Currency) As Long
'
' Texto basado en la descripción de:
'   High-Resolution Timer (Platform SDK / Windows User Interface)
' La función QueryPerformanceCounter devuelve el valor actual del temporizador
' de alta precisión (si es que el sistema tiene uno).
' Se puede usar para calcular intervalos de tiempo usando la precisión de
' un temporizador de alta precisión (o resolución).
' Por ejemplo, suponiendo que QueryPerformanceFrequency indicara que la frecuencia
' del temporizador de alta precisión es de 50000 por segundo. Si la aplicación
' llamara a QueryPerformanceCounter inmediatamente antes y justo después de la
' sección de código a temporizar, y que estos valores fuesen 1500 y 3500,
' esto indicaría que el tiempo transcurrido es de .04 segundos (2000 ciclos)
'
'
'Private Declare Function timeBeginPeriod Lib "winmm.dll" _
'    (ByVal uPeriod As Long) As Long
'Private Declare Function timeEndPeriod Lib "winmm.dll" _
'    (ByVal uPeriod As Long) As Long
'
'------------------------------------------------------------------------------
' La precisión de GetTickCount es (aproximadamente)
' de 10 milisegundos en Windows NT 3.5 o superior
' de 16 ms. en Windows NT 3.1
' de 55 ms. en Windows 95 o superior
'
Private Declare Function GetTickCount Lib "kernel32.dll" () As Long

'------------------------------------------------------------------------------
' Valores del tipo de timer a usar
Public Enum eTipoTimer
    eGetTickCount '= 0
    eTimeGetTime '= 1
End Enum

' Variables locales para los cálculos
'
Private mc_StartTimer As Currency
Private mc_EndTimer As Currency
Private mc_Frecuencia As Currency
'
Private m_TimerType As eTipoTimer
Private m_StartTimer As Long
Private m_EndTimer As Long
Private m_LastTime As Long      ' Último tiempo calculado           (11/Mar/99)

Public Function ElapsedTime(Optional Intervalo As Long = 1000) As String
    ' Mostrar el tiempo transcurrido
    '
    ' Intervalo se usará para la forma de mostrar el tiempo transcurrido
    '
    ' Detener el timer
    If m_EndTimer = 0 Then
        StopTimer
    End If
    '
    ' No admitir números negativos ni cero
    If Intervalo < 1 Then Intervalo = 1000
    '
    ' Devolver una cadena formateada con el tiempo
    ElapsedTime = Format$(CDbl(m_EndTimer) / Intervalo, "0.0####")
    ' si se quiere usar el TimerPerformanceCounter
    'ElapsedTime = Format$((mc_EndTimer / mc_Frecuencia) * 1000 / Intervalo, "0.0####")
End Function

Public Sub StopTimer()
    ' Detener el timer
    '
    ' El tiempo de diferencia puede no ser "exacto",
    ' ya que se reinicia cada 49.7 dias... (2^32 milisegundos)
    ' El timer se pone a cero cuando se inicia el sistema.
    If m_TimerType = eTimeGetTime Then
        'mc_EndTimer = TimerPerformanceCounter - mc_StartTimer
        m_EndTimer = timeGetTime() - m_StartTimer
    Else
        m_EndTimer = GetTickCount() - m_StartTimer
    End If
    '
    m_LastTime = m_EndTimer
End Sub

Public Sub StartTimer()
    ' Iniciar el timer
    m_EndTimer = 0
    If m_TimerType = eTimeGetTime Then
        'mc_Frecuencia = TimerPerformanceFrecuency
        'mc_StartTimer = TimerPerformanceCounter
        m_StartTimer = timeGetTime()
    Else
        m_StartTimer = GetTickCount()
    End If
End Sub

Public Property Get TimerType() As eTipoTimer
    ' Devolver el tipo de timer que se usa
    TimerType = m_TimerType
End Property

Public Property Let TimerType(ByVal vNewValue As eTipoTimer)
    ' asignar el tipo de timer a usar
    m_TimerType = vNewValue
End Property

Private Sub Class_Initialize()
    ' Valor por defecto
    'm_TimerType = eGetTickCount
    m_TimerType = eTimeGetTime      ' Este es más preciso               (11/Mar/99)
End Sub

Public Property Get TotalElapsedTime() As Long
    ' Mostrar el tiempo transcurrido en el último cálculo,
    ' incluso mientras se está calculando
    TotalElapsedTime = m_LastTime ' m_EndTimer
End Property

Public Property Get TimerPerformanceFrecuency() As Long
    ' Devuelve la frecuencia del temporizador de alta precisión     (27/Oct/00)
    '
    Dim curFrecuency As Currency
    Dim ret As Long
    '
    ret = QueryPerformanceFrequency(curFrecuency)
    If ret = 0 Then
        ' No hay un temporizador de alta precisión
        TimerPerformanceFrecuency = 0
    Else
        ' Devolver el valor de la frecuencia por cada segundo
        ' (multiplicar por 10000 ya que el valor devuelto viene con decimales)
        TimerPerformanceFrecuency = curFrecuency * 10000
    End If
End Property

Public Property Get TimerPerformanceCounter() As Currency
    ' Contador del temporizador de alta precisión actual            (27/Oct/00)
    ' Esta función se usará junto con TimerPerformanceFrecuency,
    ' para averiguar el lapso transcurrido entre dos periodos de tiempo
    '
    ' Los milisegundos transcurridos será:
    '   la diferencia entre dos llamadas a esta propiedad
    '   dividido por el valor indicado por TimerPerformanceFrecuency
    '
    ' Nota: Este resultado será casi igual que una llamada a timeGetTime,
    '       aunque algo más precisa.
    '
    Dim curFrecuency As Currency
    Dim ret As Long
    '
    ret = QueryPerformanceCounter(curFrecuency)
    If ret = 0 Then
        ' No hay un temporizador de alta precisión
        TimerPerformanceCounter = 0
    Else
        ' Devolver el valor del contador
        ' (multiplicar por 10000 ya que el valor devuelto viene con decimales)
        TimerPerformanceCounter = curFrecuency * 10000
    End If
End Property


Para usarlo se haría algo como esto:
 

'Una referencia para poder usar la clase
Dim tGetTimer As cGetTimer


'Creamos una nueva instancia de la clase
Set tGetTimer = New cGetTimer

'Asignamos el tipo de temporizador a usar
 With tGetTimer

    'Indicarle el tipo de función a usar
    .TimerType = eGetTickCount

    '---El bucle de Integer
    'Iniciamos el temporizador
    .StartTimer

    For nRep = 1 To MaxRep
       For intCount = 1 To MaxBucle
       Next
    Next
    'Detenemos el temporizador
    .StopTimer
    DoEvents
    'Mostramos el tiempo en segundos -----v
    lblResultado(eInteger) = .ElapsedTime(0)
    '   Si se le indica un valor inferior a 1,
    '   se usa el valor por defecto: 1000
    '
    'para mostrarlo en milisegundos:
    'lblResultado(eInteger) = .ElapsedTime(1)
End With

'Ya no necesitamos la clase
Set tGetTimer = Nothing

ir al índice