High Performance Timing under Windows - ' Timer Errors ' (
Page 3 of 3 )
Timer Errors
Timing under Windows has been a well-known problem with known solutions for over ten years. But two recent advances in technology have made accurate timing somewhat difficult.
ADVERTISEMENT
The first problem came when Intel introduced their SpeedStep technology. SpeedStep allows software to dynamically change the clock speed of the processor. This is a huge boon for laptops, which can vary the clock speed and thus the amount of power consumed and heat created based on the ever-changing needs of the system. Editing a Word document, for example, does not require the full power of your 2GHz processor, but watching a movie from the DVD probably does.
Changing the clock speed can cause some interesting problems for a program that uses the RDTSC processor instruction to obtain timings. Those programs typically obtain the processor speed at startup, and then use that value as the divisor when computing elapsed times. But because RDTSC returns the number of processor clock ticks—a number that is directly related to the processor clock frequency—any computation of elapsed time must use the current clock frequency. Obtaining the clock frequency is a relatively expensive operation. Even if it were instantaneous, it couldn't take into account the possibility of the clock frequency changing during the timing run. SpeedStep has rendered RDTSC almost useless. Don't use it.
A more difficult problem occurs in dual-core systems. When gamers started getting dual-core processors, they began experiencing interesting and disturbing things, including jerky animation and negative elapsed times. It's very odd indeed to see your character jump back in time. Why this happens isn't immediately obvious until you realize that those two processors aren't exactly in sync. So, suppose the following happens:
QueryPerformanceCounter(&startTime); // obtains from core 1
// execute code
QueryPerformanceCounter(&endTime); // obtains from core 2
And since core 2 is lagging a little behind core 1, the returned endTime value is less than the startTime. All of a sudden, you have a negative elapsed time. An almost equally bad situation occurs if core 1 is lagging behind core 2. In that case, the reported elapsed time will be larger (sometimes up to 500 milliseconds) than the actual elapsed time.
On a multiprocessor computer, it should not matter which processor is called. However, you can get different results on different processors due to bugs in the basic input/output system (BIOS) or the hardware abstraction layer (HAL). To specify processor affinity for a thread, use the SetThreadAffinityMask function.
Setting the thread affinity isn't always an option, though, especially in multi-threaded programs. In addition, there are some reports that setting the thread affinity doesn't always work, although I've been unable to confirm that.
Microsoft documented this problem in their Knowledge Base article 896256, which was updated last in August of 2006. A hotfix is available, which should alleviate the problem. The article tells you how to go about obtaining the fix from Microsoft.
If you're writing games or other programs that require high-resolution timing, you really should read Microsoft's Game Timing and Multicore Processors article for tips and techniques that will help you make the most of the timing services provided by Windows.
Finally, if you're interested in coding around the problem, Charles Bloom (and others) have created some code libraries that attempt to identify when the problem occurs and provide reasonable numbers. See Charles' Timer.h and Timer.cpp for the code. The code has many comments that explain the inner workings of the timers and how the different timer functions work together.