An Accurate Timer Class

Tuesday Apr 15th 2003 by Marcel Meuwissen

Create a timer that takes into consideration the fact that routines such as sleep() take time!

Environment: Timers, miscellaneous


This class supplies an accurate timer (figured in milliseconds). There are two advantages to this class instead of the normal Sleep():

  • It can sleep the remaining time of a chosen period, causing exact timeframes
  • It's more accurate

The need for this class lies in the fact that I needed to do an action exactly every second. In my case, it is used to communicate with a PLC, and the action has some I/O every second. This can take a considerable amount of time. But, you can use it for any purpose.

When you want something to execute every second, and your action takes 300 ms, you face the problem that with a Sleep(1000) after the action, the complete process takes more then 1300 milliseconds.

A further problem was the accuracy of a Sleep. This is not high. A Sleep of 300ms can take 310ms. a Sleep of 1ms takes at least 8ms. I did a check on different machines; it is always different and always bad.

To avoid this inaccuracy of Sleep, I only used Sleep() to approach the end time up to 15 milliseconds. In the last part, I used a dirty for() loop. Because this loop generates a big CPU usage, I only used this at the very end of the waiting time. Because of the small period, you cannot see any difference in the NT-performance meter.

When you don't need to use this level of accuracy, only the remain sleep function, InitTimer(false), will avoid this for() loop, to the disadvantage of the accuracy.

And How Does It Work Under Water?

The best way for a good timer to operate is to use the QueryPerformanceCounter(). It gives clockpulses. Because this is hardware-dependent, we need to know the frequency (how many pulses per second). This is achieved by using QueryPerformanceFrequency(). QueryPerformanceCounter() works with a struct LARGE_INTEGER, which has some disadvantages when calculating. It's no problem to use a LONGLONG (__int64) and cast it. This saves us from a lot of casting and time-consuming member accesses.

Because my app runs for long times, I looked to the maximum time it could run: We store in a __int64; its maximum val is 9.22337E+18 (=2^63). This is the maximum period we can sleep.

The frequency/divider of a P2-333 MHz is 1,193,180
The frequency/divider of a P4-2.4 GHz is 3,579,545

The worse case is P4. This means we can store a maximum of:

2,576,688,388,288 seconds
   42,944,806,471 minutes
      715,746,775 hours
       29,822,782 days
           81,706 years

This is enough for my humble program...

Internal variables are all saved as clockpulses, not in milliseconds. All user input/output is done in milliseconds and translated to/from clockpulses.

The function SleepNow() acts just as an ordinary Sleep() but when you name it the same, there is an unwanted case of recursive programming when you just need ten original Sleep() cycles.

When sleeping, it acts the same as a normal sleep: You cannot interact with the app anymore. You can put it in a separate thread to avoid this. In the test app, I didn't do this. It's just a simple test container about the use of the timer class. In your own app, it's enough to include CKETimer.cpp and CKETimer.h.

To Do

Yes, I left some ends open, so you guys (girls?) out there can extend it with your ideas:

  • I don't like the for loop, but I can't think of a process that is short (<1ms) and doesn't boos CPU usage.
  • Make it thread safe
  • Test it on other OS/PC's (it works on Win98, 2000 and XP, all >= P2)


Download source - 24 Kb
Mobile Site | Full Site
Copyright 2017 © QuinStreet Inc. All Rights Reserved