65. How do I make accurate timings under Win32?
With difficulty :-) This is one of those unfortunate cases where the answer
is hedged around with all sorts of OS-specific limitations and caveats.
Let's assume you need to measure time down to around a few milliseconds. The
first point to make is that the approach you adopt depends upon whether you want
to just measure time, or get a timely notification .
If you just to want to know the elapsed time between two events in your code you
can use QueryPerformanceCounter. There are some mathematical complexities
around allowing for counter rollover, but essentially this will tell you the
elapsed time you need. Another option is to get a class based upon the Pentium-family
RDTSC instruction. You can download one from P.J.Naughter's page here
If QueryPerformanceCounter looks like it might be your thing, here's some
code that should get the job done:
// QueryPerf sample code. Error-handling omitted. Note that
// QueryPerformanceFrequency can fail if your system doesn't
// support performance counters. READ THE HELP!!
// In the function you want to measure:
TCHAR szDebug [100];
__int64 liTicksPerSec64;
__int64 liBeginTime64;
__int64 liEndTime64;
QueryPerformanceFrequency((LARGE_INTEGER*)&liTicksPerSec64);
QueryPerformanceCounter((LARGE_INTEGER*)&liBeginTime64);
//
// do some time-consuming uninterrupted stuff...
//
QueryPerformanceCounter ((LARGE_INTEGER*)&liEndTime64);
itus = CalcMicroSecInterval (liTicksPerSec64,
liBeginTime64,
liEndTime64);
wsprintf (szDebug, "Time to do stuff: %dms", itus/1000);
OutputDebugString (szDebug);
// Helper function:
int CalcMicroSecInterval (__int64 liTps64,
__int64 liStart64,
__int64 liEnd64)
{
double dTicksPerUs;
double dInterval;
__int64 iIntTicks64;
int iInterval;
int iTicksIn;
dTicksPerUs = (double)liTps64 / (double)1000000.0;
if (liEnd64 > liStart64)
iIntTicks64 = liEnd64 - liStart64;
else
iIntTicks64 = liStart64 - liEnd64;
iTicksIn = (int) iIntTicks64;
dInterval = ((double) iIntTicks64) / dTicksPerUs;
iInterval = (int) dInterval;
return iInterval;
}
If you want a notification, things get more complex. There are the standard
Windows timers documented under WM_TIMER, but these suffer from some problems:
- Limit of accuracy is around 55ms under 95/98/ME, 10-15ms under the NT family.
- WM_TIMER messages can get "merged" because they're implemented
as a flag inside the GetMessage function.
- You need to run a message pump.
Alternatives are
- multimedia timers (see the help beginning with timeSetEvent) which will
take you down to a theoretical resolution of 1ms.
- waitable timer objects (NT only - see the help for CreateWaitableTimer and
also tip 16).
But there's a fly in this ointment - even NT is not a real-time OS, and
kernel-mode software can tromp all over your best-laid plans. There are badly
written drivers around that will disable interrupts at the drop of a hat, and
all your carefully constructed code won't be worth a damn if the processor can't
see the outside world anymore. Typically the culprits are flaky video drivers,
but Microsoft's own ATAPI.SYS isn't above this kind of skullduggery. Don't
believe me? try writing a little program that sets up multimedia timers and
checks their accuracy with performance counters (or download my crude little one
here). Most of the time you'll
see they're just fine - then they'll go wonky for no apparent reason. Welcome to
the twilight zone...
People who absolutely MUST have reliable accurate ms or sub-ms timings under Windows
nearly always have to resort to hardware solutions or kernel mode code.