Current version

v1.10.4 (stable)

Navigation

Main page
Archived news
Downloads
Documentation
   Capture
   Compiling
   Processing
   Crashes
Features
Filters
Plugin SDK
Knowledge base
Contact info
 
Other projects
   Altirra

Archives

Blog Archive

Win32 timer queues are not suitable for high-performance timing

I read a suggestion on a blog that Win32 timer queues should be used instead of timeSetEvent(), so I decided to investigate.

First, a review of what timer queues do. A timer queue is used when you have a bunch of events that need to run at various times. What the timer queue does is maintain a sorted list of timers and repeatedly handles the timer with the nearest deadline. Not only is this more efficient when you have a bunch of timers because you don't have a bunch of pieces of code all maintaining their own timing, but it's also a powerful technique because it can allow you to multiplex a limited timing resource. It's especially good when you have a bunch of low-priority, long-duration timers like UI timers, where you don't want to spend a lot of system resources and precise timing is not necessary.

The classic timer queue API in Windows is SetTimer(). This is mainly intended for UI purposes, and as a result it's both cheap and imprecise. If you're trying to do multimedia timing, SetTimer() is not what you want. It's also a bit annoying to use because you need a message loop and there's no way to pass a (void *) cookie to the callback routine (a common infraction which makes C++ programmers see red). The newer timer API, however, is CreateTimerQueue(). This allows you to create your own timer queue without having to tie it to a message loop, and looks like it would be a good replacement for timeSetEvent().

Unfortunately, if you're in a high-performance timing scenario like multimedia, Win32 timer queues suck.

The first problem is the interface. CreateTimerQueueTimer() takes a parameter called DueTime, which specifies the delay until the timer fires for the first time. Delay relative to what? Well, when you call the CreateTimerQueueTimer() function. Or rather, some undetermined time between when you call the function and it returns. The problem with an interface like this is that you have no idea if something sneaks in between and stalls your thread for a while, like another thread or a page fault. Therefore, you get a random amount of jitter in your start time. Another problem is that if you are creating a repeating timer, you can only set the period in milliseconds. That's not precise enough for a lot of applications. If you're trying to lock to a beam position on a 60Hz display, for instance, this forces you to take a third of a second error per frame, or a 2% error.

That's not the worst part, though. Let's say we just want a regular 100ms beat. That shouldn't be hard for a modern CPU to do. Well, here are the results:

   0    0
 109  109
 219  110
 328  109
 437  109
 547  110
 656  109
 766  110
 875  109

The first number is the time offset in milliseconds, measured by timeGetTime(), which has approx. 1ms accuracy and precision. The second number is the delta from the last timer event. Notice a problem? The period is consistently longer than requested. In this case, we're 10% slower than intended. If you request a lower period, it gets much worse. Here's the results for a 47ms periodic timer:

   0    0
  63   63
 125   62
 188   63
 250   62
 313   63
 375   62
 438   63
 500   62
 563   63

The average period is about 63ms, which is about 30% off from our requested period. That's terrible!

There's another factor, by the way: the timer queue API shares the same characteristic as many timing APIs in Windows of being dependent upon the resolution of the system timer interrupt and is thus also affected by timeBeginPeriod(). The reason I know is that the first time I tried this, I got results that still weren't great, but were a lot better than what you see above. The 47ms timer, for instance, turned in deltas of 48-49ms. Then I realized that I was playing a song in WinAmp in the background, and had to redo the test again.

After being somewhat depressed at the mediocre performance of the timer queue API, I remembered the CreateWaitableTimer() API. This is a different API where you create a timer object directly instead of part of a timer queue, and it also runs the timer in your thread instead of a thread pool thread, which is much easier to deal with if you're trying to time work that requires APIs that must be called on a specific thread, particularly most DirectX APIs. As it turns out, the waitable timer API doesn't fare any better than the timer queue API for periodic timers, as it still takes the period in milliseconds and still has the same problems of significant, consistent error in period and sensitivity to the timer interrupt rate. However, the good side is that you specify the initial delay in 100ns units instead of milliseconds, and more importantly, you can specify an absolute deadline. This is very advantageous, because it means that you can compute timer deadlines internally in high precision off of a consistent time base, and although each individual timer may be imprecise, you can precisely control the average period.

Caveat: I've only tried this in Windows XP, so I don't know if the situation has improved in Windows Vista or Windows 7.

In current versions of VirtualDub, I don't use any of these methods for frame timing in preview mode. Instead, I have my own timer queue thread that uses timeGetTime() coupled with a Sleep() loop, with precision boosted by timeBeginPeriod() and with thread priority raised. You might think this is a bit barbaric, and it is, but it was the best way I could think of to get a reliable, precise timer on all versions of Windows. The trick to making this work is putting in feedback and adjusting the delay passed to Sleep() so that you don't accumulate error. As a result, I can do what I couldn't do with timeSetEvent(), which is to have a timer that has an average period of 33.367 ms. I suppose I could rewrite it on top of the waitable timer API, but it didn't seem worth the effort.

Update:

Here is the test program output from Windows 7 RC for a 50ms timer:

   0    0
  47   47
  94   47
 140   46
 203   63
 250   47
 296   46
 343   47
 390   47
 452   62
 499   47
 546   47
 593   47
 640   47
 702   62
 749   47
 796   47
 842   46
 889   47
 952   63

It seems that Windows 7 does use a method that prevents accumulated error, thus giving an accurate period on average at the cost of consistency.

More interesting is when you give it a very short period, one that is shorter than that of the timing source:

   0    0
  16   16
  16    0
  16    0
  31   15
  31    0
  31    0
  47   16
  47    0
  47    0
  63   16
  63    0
  63    0
  78   15
  78    0
  78    0
  94   16
  94    0
  94    0

In this case, the timer fires multiple times back to back.

Comments

This blog was originally open for comments when this entry was first posted, but was later closed and then removed due to spam and after a migration away from the original blog software. Unfortunately, it would have been a lot of work to reformat the comments to republish them. The author thanks everyone who posted comments and added to the discussion.