First Code – a simple timer class

First code – Simple timer class


Let’s dive right in with one of the simplest classes I use:  clsTimer.  But before we do that, we need to discuss how to time something without a class so that we can compare that to doing the same thing with a class.

Suppose you want to time how long it takes to perform some function.  Perhaps it is opening a recordset or performing a complex calculation.  Windows has a counter running that provides one-millisecond ticks, and it is just a long integer that is constantly incrementing, a thousand times / second.  We need a variable to store the start time in ticks so that we can come back later and get the difference between the start time and the current time.

So we create a module basTimer.  In that module we create a pair of functions and a call to Windows.

basTimer

Option Compare Database

Option Explicit

Private Declare Function apiGetTime Lib “winmm.dll” _

                                      Alias “timeGetTime” () As Long

Function ReadTimer(lngStartTime  as long) as long

ReadTimer = apiGetTime() – lngStartTime

End Function

Sub StartTimer(lngStartTime  as long)

lngStartTime = apiGetTime()

End Sub

Now, to use our timer, all we have to do is dimension a variable to store our start count and pass it in to StartTimer() to start timing something, right?  StartTimer saves the current tick count from Windows into the variable passed in.  At any time we can ask Windows to get the latest tick count and subtract the starting tick count from that to get the total ticks from the time we started.

That is the way we do things without classes.

 

basTimerTest

Option Compare Database

Option Explicit


‘Gives us something to time for testing and demonstrations

Function mTimerDemo()
Dim lngStartTimeOuterLoop As Long
Dim lngStartTimeInnerLoop As Long

Dim Pi As Single

Dim lngOuterCnt As Long
Dim lngInnerCnt As Long
Dim sngVal As Single

Pi = 4 * Atn(1)
StartTimer lngStartTimeOuterLoop
For lngOuterCnt = 1 To 1000
StartTimer lngStartTimeInnerLoop

‘This inner loop just does something enough times
‘to cause it to take a measurable time to perform
For lngInnerCnt = 1 To 1000000
sngVal = Pi * lngInnerCnt
Next lngInnerCnt

‘On my virtual development machine this now takes around 13 ms to perform the inner loop
‘Adjust as required for your situation
Debug.Print ReadTimer(lngStartTimeInnerLoop)
Next lngOuterCnt

‘The outer loop takes around 30 seconds
Debug.Print “Outer Loop = ” & ReadTimer(lngStartTimeOuterLoop)
End Function

If you didn’t already have a timer method, now you do.  But let’s discuss the class way of doing this and why we might want to use that method instead.

clsTimer

 

Option Compare Database

Option Explicit

Private Declare Function apiGetTime Lib “winmm.dll” _

                                      Alias “timeGetTime” () As Long

Private lngStartTime As Long

Private lngLastReadTime as long  ‘elapsed ticks when we last called ReadTimer

Private Sub Class_Initialize()

    StartTimer

End Sub

Function ReadTimer() as long

    lngLastReadTime = apiGetTime()- lngStartTime

    ReadTimer = lngLastReadTime

End Function

Sub StartTimer()

    lngStartTime = apiGetTime()

End Sub

function LastReadTime() as long

    LastReadTime = lngLastReadTime

end function

 

Notice that things look very similar.  We have the same Windows call; we have the same two functions to start and read the timer.  Notice, however, that the storage variable for the start time is embedded in the class and so we do not have to pass in that value anywhere.  Because the StartTimer() and ReadTimer() can ‘see’ the variable, they can set it and read it as necessary.  Notice that we also have a method to read the ‘last read time’ without re-reading the elapsed time.

Our code to use the timers looks similar.


‘Gives us something to time for testing and demonstrations

Function mclsTimerDemo()
Dim cTmrLoopOuter As clsTimer
Dim cTmrLoopInner As clsTimer

Dim Pi As Single

Dim lngOuterCnt As Long
Dim lngInnerCnt As Long
Dim sngVal As Single

Pi = 4 * Atn(1)

Set cTmrLoopOuter = New clsTimer

For lngOuterCnt = 1 To 1000
Set cTmrLoopInner = New clsTimer

‘This inner loop just does something enough times
‘to cause it to take a measurable time to perform
For lngInnerCnt = 1 To 1000000
sngVal = Pi * lngInnerCnt
Next lngInnerCnt

‘On my virtual development machine this now takes around 13 ms to perform the inner loop
‘Adjust as required for your situation
Debug.Print cTmrLoopInner.ReadTimer()
Next lngOuterCnt

‘The outer loop takes around 30 seconds
Debug.Print “Outer Loop = ” & cTmrLoopOuter.ReadTimer()
End Function

So what have we gained by using a class?  Nothing startlingly obvious.  We still have to dimension variables, and we actually have to go through an extra step of the ‘set = new’.  However, we no longer have to pass a value in to the timer functions.  This makes the timer a tiny bit more accurate over many iterations because we no longer have to push and pop the storage variable on the stack.

Let’s suppose for a minute that we want to store the ReadTime.  Notice that every time we call the ReadTimer() we get a new value because we are getting apiGetTime() at that instant.  Suppose we wanted to take the outer loop and read the time as soon as the loop exits but use the time later.  There’s really no way to do that without a variable to store the LastReadTime.  Without a class, we have to handle the pair of values ourselves.  With our class, we can easily do that.  Our class has a pair of variables, and it automatically handles setting and retrieving them for us.

Let’s get admittedly slightly esoteric here.  Suppose you wanted to store all of the times of the inner loop.  We could do that by setting up a collection inside of the class and pushing each value into the collection during ReadTimer().  That would provide a method of getting at the collection and — voila — storage for every read.

clsTimerEsoteric

Option Compare Database

Option Explicit

‘clsTimerEsoteric

Private Declare Function apiGetTime Lib “winmm.dll” _
Alias “timeGetTime” () As Long
Private mcolTimes As Collection  ‘A collection to store our read counts
Private lngStartTime As Long
Private lngLastReadTime As Long  ‘elapsed ticks when we last called ReadTimer

Private Sub Class_Initialize()
Set mcolTimes = New Collection  ‘Initialization for the collection
StartTimer
End Sub
Private Sub Class_Terminate()
Set mcolTimes = Nothing  ‘Cleanup behind ourselves
End Sub

‘A class property to return the collection so we can read the values
‘ Property Get colTimes() As Collection
Set colTimes = mcolTimes
End Property

‘Read the timer, perform the subtraction to get the total ticks elapsed
‘Push the count into a collection
‘Return the count
‘ Function ReadTimer() As Long
lngLastReadTime = apiGetTime() – lngStartTime
colTimes.Add lngLastReadTime
ReadTimer = lngLastReadTime
End Function

Sub StartTimer()
lngStartTime = apiGetTime()
End Sub

‘Just get the last count without reading the computer’s tick count again
‘This allows us to read the count at any time
‘ Function LastReadTime() As Long
LastReadTime = lngLastReadTime
End Function

One of the points of this exercise is that the class sets up and initializes our variables for us.  The variables and code areencapsulated together; that is, they are contained together in the same place.  We have the variables stored in the same class module as the code that manipulates the variables.  We can document them with comments in a generic way.  We can make the class dead simple, or we can make it a little more esoteric; but, in the end, we don’t have data and code scattered around our program.  All timing code and all timing variables are contained in the class.  In the case of clsTimerEsoteric, we could even pass a pointer to it off to another part of the program that reads the times out of the collection and saves them to a recordset or uses them in a report or exports them to a csv file.  If you find yourself writing the times to a csv file regularly, you could even add the code to do that directly into clsTimerEsoteric.

OK, I warned you it would be slightly esoteric!  But, with a class, we have a place to do things, all the code, all of the variables and constants, and we can document what we are doing.

Leave a Reply

Your email address will not be published. Required fields are marked *