On WinAPI timers and their resolution
While working on PuttyRec (that is practically rewriting it from scratch) I wondered about resolution of various Windows timer APIs. Of course that’s pretty irrelevant in the context of a terminal recorder, not that you need to cope with more that a few events per second there. But being the curious man I am (and sometimes a perfectionist when it comes to code) I set to investigate the matter myself.
What selection do we have then? There is the SetTimer, of course. PuttyRec used it before and that worked without much problems. SetTimer has its problems though, the biggest being that it’s only available to use on UI threads (that is, threads owning a message queue). Although creating a message queue is easy (just call any message-related API), that’s hardly convenient. It also has reputation of being more than slightly inaccurate.
Then we have timeSetEvent, a multimedia timer. It doesn’t require message queue (but also runs on a separate thread) and is supposed to be more accurate. The MSDN page contains a curious notice though: Note This function is obsolete. New applications should use CreateTimerQueueTimer to create a timer-queue timer.
A “better” function, eh? I used it before and it also seemed to work well.
Interestingly MSDN doesn’t specify what resolution these functions have. That’s pretty useless if you need a high-precision timer. I wrote a quick and dirty test program that runs all three of the above timers for a period of one second with varying timer period. As a reference I included a “busy wait” loop that just spins for desired amount of time. Time measurement was done by QueryPerformanceCounter. Without further ado, here are the results (32bit release, but 64bit/debug is roughly the same):
Period: 100ms CreateTimerQueueTimer : count= 9, avg error= 0.085%, std dev= 0.193 timeSetEvent : count= 9, avg error= 0.013%, std dev= 0.017 Busy wait : count= 9, avg error= 0.000%, std dev= 0.000 SetTimer : count= 8, avg error= 9.629%, std dev= 2.060 Period: 50ms CreateTimerQueueTimer : count= 19, avg error= 0.109%, std dev= 0.140 timeSetEvent : count= 19, avg error= 0.027%, std dev= 0.022 Busy wait : count= 19, avg error= 0.000%, std dev= 0.000 SetTimer : count= 15, avg error=24.800%, std dev= 3.117 Period: 20ms CreateTimerQueueTimer : count= 49, avg error= 0.177%, std dev= 0.242 timeSetEvent : count= 49, avg error= 0.106%, std dev= 0.095 Busy wait : count= 49, avg error= 0.135%, std dev= 0.656 SetTimer : count= 31, avg error=55.518%, std dev=12.167 Period: 10ms CreateTimerQueueTimer : count= 99, avg error= 0.216%, std dev= 0.197 timeSetEvent : count= 99, avg error= 0.192%, std dev= 0.257 Busy wait : count= 99, avg error= 0.122%, std dev= 0.537 SetTimer : count= 63, avg error=60.329%, std dev=35.078 Period: 5ms CreateTimerQueueTimer : count= 199, avg error= 0.796%, std dev= 3.244 timeSetEvent : count= 199, avg error= 0.257%, std dev= 0.380 Busy wait : count= 199, avg error= 0.014%, std dev= 0.106 SetTimer : count= 63, avg error=212.087%, std dev=74.290 Period: 2ms CreateTimerQueueTimer : count= 499, avg error= 2.291%, std dev=12.107 timeSetEvent : count= 499, avg error= 0.790%, std dev= 0.831 Busy wait : count= 499, avg error= 0.006%, std dev= 0.005 SetTimer : count= 63, avg error=680.188%, std dev=209.531 Period: 1ms CreateTimerQueueTimer : count= 999, avg error= 4.185%, std dev=11.480 timeSetEvent : count= 999, avg error= 1.268%, std dev= 1.695 Busy wait : count= 999, avg error= 0.225%, std dev= 3.743 SetTimer : count= 62, avg error=1450.387%, std dev=415.344
What can we say about that?
- Obviously, inaccuracy raises as the period gets smaller.
- SetTimer is really inaccurate. Even with a 100ms period it was off by 10%, and below that it’s just useless.
- Busy wait was of course the most accurate.
- CreateTimerQueueTimer was remarkably worse than the “obsolete” timeSetEvent! What’s going on here?
- timeSetEvent was performing really well even with the smallest periods. Errors below 1%, low standard deviation. Deprecated my ass.
So there you have it. I’d recommend using timeSetEvent for good precision, and if that’s not enough for you – timeSetEvent with short busy wait at the end to refine the result.
And the source code:
Windows timers microbenchmark code | Show> |
---|---|
Excellent review! Thank you very much!
Aryeh said this on December 29, 2016 at 08:51 |