views:

323

answers:

3

I was using the system timer (clock() function, see time.h) to time some serial and USB comms. All I needed was approx 1ms accurace. The first thing I noticed is that individual times can be out (plus or minus) 10ms. Timing a number of smaller events led to less accurate timing as events went by. Aggregate timing was slightly better. After a bit of a root on MSDN etc I stumbled across the timer in windows multi-media library (timeGetTime(), see MMSystem.h). This was much better with decent accuracy to the 1ms level.

Weirdness then ensued, after initially working flawlessy for days (lovely logs with useful timings) it all went pearshaped as this API also started showing this odd granularity (instead of a bunch of small comms messages taking 3ms,2ms,3ms,2ms, 3ms etc. it came out as 0ms, 0ms, 0ms, 0ms, 15ms etc. Rebooting the PC restored nomal accuarce but at some indeterminate time (after an hour or so) the anomoly returned.

Anyone got any idea or suggestions of how to get this level of timing accuracy on Windows XP (32bit Pro, using Visual Studio 2008).

My little timing class:

class TMMTimer
{
public:
    TMMTimer( unsigned long msec);
    TMMTimer();

    void Clear() { is_set = false; }
    void Set( unsigned long msec=0);

    bool Expired();
    unsigned long Elapsed();

private:
    unsigned long when;
    int roll_over;
    bool is_set;
};



/** Main constructor.
 */
TMMTimer::TMMTimer()
{
    is_set = false;
}




/** Main constructor.
 */
TMMTimer::TMMTimer( unsigned long msec)
{
    Set( msec);
}




/** Set the timer.
 *
 * @note   This sets the timer to some point in the future.
 *         Because the timer can wrap around the function sets a
 *         rollover flag for this condition which is checked by the
 *         Expired member function.
 */
void TMMTimer::Set( unsigned long msec /*=0*/)
{
    unsigned long now = timeGetTime();       // System millisecond counter.

    when = now + msec;

    if (when < now)
        roll_over = 1;
    else
        roll_over = 0;

    is_set = true;
}




/** Check if timer expired.
 *
 * @return  Returns true if expired, else false.
 *
 * @note    Also returns true if timer was never set. Note that this
 *          function can handle the situation when the system timer
 *          rolls over (approx every 47.9 days).
 */
bool TMMTimer::Expired()
{
    if (!is_set)
        return true;

    unsigned long now = timeGetTime();       // System millisecond counter.

    if (now > when)
    {
        if (!roll_over)
        {
            is_set = false;
            return true;
        }
    }
    else
    {
        if (roll_over)
            roll_over = false;
    }

    return false;
}




/** Returns time elapsed since timer expired.
 *
 * @return  Time in milliseconds, 0 if timer was never set.
 */
unsigned long TMMTimer::Elapsed()
{
    if (!is_set)
        return 0;

    return timeGetTime()-when;
}
+1  A: 

Check out the high resolution timers from the Win32 API.

http://msdn.microsoft.com/en-us/library/ms644904(VS.85).aspx

You can use it to usually get microsecond resolution timers.

Xorlev
+3  A: 

If you want high resolution timing on Windows, you should consider using QueryPerformanceFrequency and QueryPerformanceCounter.

These will provide the most accurate timings available on Windows, and should be much more "stable" over time. QPF gives you the number of counts/second, and QPC gives you the current count. You can use this to do very high resolution timings on most systems (fraction of ms).

Reed Copsey
+7  A: 

Did you call timeBeginPeriod(1); to set the multimedia resolution to 1 millisecond? The multimedia timer resolution is system-global, so if you didn't set it yourself, chances are that you started after something else had called it, then when that something else called timeEndPeriod(), the resolution went back to the system default (which is normally 10 ms, if memory serves).

Others have advised using QueryPerformanceCounter(). This does have much higher resolution, but you still need to be careful. Depending on the kernel involved, it can/will use the x86 RDTSC function, which is a 64-bit counter of instruction cycles. For better or worse, on a CPU whose clock rate varies (which started on laptops, but is now common almost everywhere) the relationship between the clock count and wall time varies right along with the clock speed. If memory serves, if you force Windows to install with the assumption that there are multiple physical processors (not just multiple cores), you'll get a kernel in which QueryPerformanceCounter() will read the motherboard's 1.024 MHz clock instead. This reduces resolution compared to the CPU clock, but at least the speed is constant (and if you only need 1 ms resolution, it should be more than adequate anyway).

Jerry Coffin
there is a small tool to check the current timer resolution which can be found here http://www.lucashale.com/timerresolution/
Holger Kretzschmar