October 22nd, 2025
like1 reaction

What makes cheap_steady_clock faster than std::chrono::high_resolution_clock?

Some time ago, I noted that There is a std::chrono::high_resolution_clock, but no low_resolution_clock.

The Visual C++ library treats std::chrono::high_resolution_clock as an alias for std::chrono::steady_clock, which uses Query­Performance­Counter() to retrieve the current time, and the multiplies it by the reciprocal of Query­Performance­Frequency() to convert it to a clock tick count. So you are paying for a multiplication after Query­Performance­Counter() returns.

But there’s a lot going on inside Query­Performance­Counter() itself, too. It has to use a different algorithm depending on things like whether the timestamp counter (such as RDTSC on x86 or CNTVCT_EL0 on AArch64) is reliable, whether the system is running inside a virtual machine, whether the process is running under emulation, and various other conditions. In the worst case, it needs to make a system call into the kernel.

On the other hand, Get­Tick­Count64() merely reads two 64-bit values from memory and multiplies them. One is a raw value that is updated by the kernel at each system timer interrupt (worst case), and the other is a conversion factor calculated at system startup to convert the raw value into milliseconds.

These two 64-bit values come from a special page that is mapped into user mode from kernel mode that contains handy values, including the current tick count. As it turns out, this is significantly faster than going through all the logic of Query­Performance­Counter. It’s a great choice if you do not need high resolution.

And deciding how long to sleep a thread is a case where you do not need high resolution. Most of the functions for sleeping a thread already operate in milliseconds, so getting the value in milliseconds saves you a lot of conversions. Calculating values with sub-millisecond accuracy is pointless if the result is going to be converted to milliseconds anyway. And the accuracy of most (all?) of these sleep functions is only as good as the system timer anyway, so really they are good to only 10 or 50 milliseconds.¹

It’s like doing precise calculations to determine that you need to set your phone alarm to wake you in exactly 32 minutes, 21.1315 seconds. Your phone alarm can’t wake you to sub-second resolution, so all that work to calculate those extra .1315 seconds was wasted.

Now, I didn’t know all of these details when I originally wrote that article. But it stands to reason that Get­Tick­Count64 is a lot cheaper than Query­Performance­Counter because Get­Tick­Count64 asks for less. Even if Get­Tick­Count64 ends up not being faster than Query­Performance­Counter, it surely won’t be slower.

¹ Or one millisecond if your process has called timeBeginPeriod(1) to ask that the system timer be sped up to 1 millisecond.

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

4 comments

Sort by :
  • Shawn Van Ness

    Does original GetTickCount (32 not 64) do a multiplication? I always thought of it as a straight-up atomic read of a DWORD from the TEB ..

    I also (maybe mistakenly) thought it was updated by the kernel thread scheduler any time control was being given to a thread .. so eg. in response to waking a WaitForSingleObject() or ResumeThread() .. or returning from Sleep() or executing an APC or whatever else.

    I realize now I have so many questions .. assumptions I never tested. Do all threads in a process have a coherent view of the tick-count?...

    Read more
  • Yexuan Xiao 10 hours ago

    I’ve looked into it before, and timeBeginPeriod doesn’t affect modern wait APIs like WaitOnAddress or SRWLock, which always maintain a wait precision between 10 and 15 milliseconds. Another significant drawback of timeBeginPeriod is that it is not thread-bound, so if multiple threads use it simultaneously, the results can lead to unexpected outcomes. Therefore, I believe it should no longer be used.

  • Dave Gzorple

    I’d never heard of timeBeginPeriod before so I looked it up. Alongside a general error it can also return TIMERR_NOCANDO which seems like a huge i18n fail to me. I mean, it’s not quite TIMERR_BOTTOM_OF_THE_NINTH_BASES_LOADED but how many non-US-English speakers are going to know what NOCANDO means? Why not just TIMERR_RESOLUTION_UNAVAILABLE?

    • GL

      So I looked this up and figured out "no can do" is actually a recognized slang, instead of pure grammar error. My take on this (as a non-US non-English-native-speaker English speaker): if one is unaware of "no can do" as a slang, one will recognize it as an erroneous form of "cannot do" and understand it; if one is aware of the slang, then of course one knows what it means.

      My confusion about multimedia API is why most of its flat API (function exports instead of COM) are camelCase instead of PascalCase as is often the case in Windows. The...

      Read more