Thanks to visit codestin.com
Credit goes to github.com

Skip to content

System Timer with 1 millisecond interval. #67088

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
KSemenenko opened this issue Mar 24, 2022 · 31 comments
Open

System Timer with 1 millisecond interval. #67088

KSemenenko opened this issue Mar 24, 2022 · 31 comments
Labels
Milestone

Comments

@KSemenenko
Copy link

System timers that can be called once every 1 millisecond.

This is very difficult to do at the moment,. especially when it comes to cross-platform.
Perhaps we should add such a high-precision system timer, it will come in handy for games or playing MIDI file.

As you can see, for small intervals, 15.6 ms is the best average. As you know, this is the standard Windows system timer resolution, which you can read in detail in the document from Microsoft called Timers, Timer Resolution, and Development of Efficient Code (http://download.microsoft.com/download/3/0/2/3027d574-c433-412a-a8b6-5e0a75d5b237/timer-resolution.docx)

The default system-wide timer resolution in Windows is 15.6 ms, which means that every 15.6 ms the operating system
receives a clock interrupt from the system timer hardware.
The System.Timers.Timer class has the same resolution as the system clock.
This means that the Elapsed event will fire at an interval defined by the resolution of the system clock if the Interval property is less than the resolution of the system clock.

So as you can see, it cannot be done without using operating system specific functions. or implementing through infinite loop and Task.Wait/Thread.Sleep methods.

@KSemenenko KSemenenko added the tenet-performance Performance related issue label Mar 24, 2022
@ghost ghost added the untriaged New issue has not been triaged by the area owner label Mar 24, 2022
@ghost
Copy link

ghost commented Mar 24, 2022

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

@ghost
Copy link

ghost commented Mar 24, 2022

Tagging subscribers to this area: @mangod9
See info in area-owners.md if you want to be subscribed.

Issue Details

System timers that can be called once every 1 millisecond.

This is very difficult to do at the moment,. especially when it comes to cross-platform.
Perhaps we should add such a high-precision system timer, it will come in handy for games or playing MIDI file.

As you can see, for small intervals, 15.6 ms is the best average. As you know, this is the standard Windows system timer resolution, which you can read in detail in the document from Microsoft called Timers, Timer Resolution, and Development of Efficient Code (http://download.microsoft.com/download/3/0/2/3027d574-c433-412a-a8b6-5e0a75d5b237/timer-resolution.docx)

The default system-wide timer resolution in Windows is 15.6 ms, which means that every 15.6 ms the operating system
receives a clock interrupt from the system timer hardware.
The System.Timers.Timer class has the same resolution as the system clock.
This means that the Elapsed event will fire at an interval defined by the resolution of the system clock if the Interval property is less than the resolution of the system clock.

So as you can see, it cannot be done without using operating system specific functions. or implementing through infinite loop and Task.Wait/Thread.Sleep methods.

Author: KSemenenko
Assignees: -
Labels:

area-System.Threading, tenet-performance, untriaged

Milestone: -

@tfenise
Copy link
Contributor

tfenise commented Mar 24, 2022

I don't think Thread.Sleep is helpful here. Thread.Sleep is as imprecise.

Quick test:

var stopwatch = new Stopwatch();
stopwatch.Start();
for (int i = 0; i < 10; i++)
{
    Thread.Sleep(1);
    Console.WriteLine(stopwatch.ElapsedMilliseconds);
}

Results: (Windows x64)
2
17
33
49
64
80
96
111
126
141

@danmoseley
Copy link
Member

related
#27191
#15207

@tannergooding
Copy link
Member

I'm not sure this is something we can reliably provide as an API due to differing support of various hardware and Operating Systems/platforms.

Maybe something could be provided with some sort of query that indicates whether such functionality is supported. But even then, OS level thread-scheduling and power management functions might get in the way.

For reference, on Windows essentially anything needing higher than 16ms resolution (the "default" time slice) needs to use the "multimedia" timers and opt-in. There are then a number of limitations and restrictions around them and it impacts the scheduling/priority of your entire process (and potentially other processes as well).

Some platforms, such as the Raspberry PI, WASM, Android/iOS in "power saving mode", etc, might not support running events this frequently.

@KSemenenko
Copy link
Author

KSemenenko commented Mar 29, 2022

For me it would be good, a new type like MultimediaTimer, which will be able to give intervals of 1ms.
Of course it would use the system functions.
And of course it will have an impact on the battery, but it will be a conscious choice of the developers if they needs such a timer.

@mangod9 mangod9 removed the untriaged New issue has not been triaged by the area owner label Jul 6, 2022
@mangod9 mangod9 added this to the Future milestone Jul 6, 2022
@Donis-
Copy link

Donis- commented Aug 16, 2022

Would be nice that such feature could be enabled via timeBeginPeriod / timeEndPeriod pairs or the not so documented NtSetTimerResolution.
To my surprise, after setting 1ms desired resolution via above APIs, even though Thread.Sleep resolution seems to be affected, the System.Threading.Timers still fire with resolution >10ms.

OS: Windows 11
.Net 6.0.3

My understanding so far:
Defaults observation:
Number of times one can Thread.Sleep(1) per 1 second: ~64
Number of GetTickCount64 changes per 1 second: ~64

After setting 1ms resolution via above APIs:
Number of times one can Thread.Sleep(1) per 1 second: ~666
Number of GetTickCount64 changes per 1 second: ~64

So even though the sleep in timer thread is likely affected correctly by these APIs:

void ThreadpoolMgr::TimerThreadFire()
..
        SleepEx(timeout, TRUE);

The problem is that the counts used to determine currentTime elapsing have a resolution of ~10-15ms because GetTickCount / GetTickCount64 is used:

DWORD ThreadpoolMgr::FireTimers()
    DWORD currentTime = GetTickCount();
...
            if (TimeExpired(LastTickCount, currentTime, timerInfo->FiringTime))
            {

Would it be possible to replace DWORD currentTime = GetTickCount(); with QPC counters / QueryPerformanceCounter if available? or via configuration?

@deeprobin
Copy link
Contributor

NtSetTimerResolution is undocumented -> we cannot use it.

@KSemenenko
Copy link
Author

but then how is it done in programs that work with MIDI files, for example?
Or in games?

Also, if it's not a documented feature, can't Microsoft document it?

@tannergooding
Copy link
Member

Many games and other multimedia apps don't use things like NtSetTimerResolution, they actively avoid sleep and other "expensive" operations.

Instead they use things like (varying from scenario to scenario of course):

  • the high-resolution time stamp APIs (QueryPerformanceFrequency and QueryPerformanceCounter or CLOCK_MONOTONIC on Linux)
  • system level fences/events (like CreateEventW and WaitForSingleObject or WaitOnAddress)
  • the documented multimedia timer functions (timeBeginPeriod and timeEndPeriod)
  • etc

Drivers have access to additional APIs (like ExSetTimerResolution) and often toggle various settings when you create your first DirectX/Vulkan/OpenGL device.

@deeprobin
Copy link
Contributor

How about emitting nops to make the delay?

@tannergooding
Copy link
Member

nop takes decoding time but generally no execution time. You wouldn't want to execute a million nop just to wait 0.25-1 millisecond.

Typically when you want to "delay" for a very brief period of time, you emit pause (or yield on Arm64). On modern CPUs this waits for about 100-140 clock cycles and correctly plays into power management/efficiency settings (which is important for mobile, laptops, and other scenarios).

If you need to wait for a longer period of time, but without giving up your time slice, you'd use the system level fences. This is what DirectX12 and Vulkan use in coordination with the GPU fences.

@KSemenenko
Copy link
Author

KSemenenko commented Aug 25, 2022


while (_running)
{
    if (stopwatch.ElapsedMilliseconds - lastTime >= intervalMs)
    {
        callback();
        lastTime = stopwatch.ElapsedMilliseconds;
    }

    if (!Thread.Yield())
        Thread.Sleep(0);
}

@ChrisDenton
Copy link

On Windows you can also use a waitable timer object with CREATE_WAITABLE_TIMER_HIGH_RESOLUTION. However, this only works since Windows 10 1803 and the actual timer resolution will depend on the underlying hardware.

See https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createwaitabletimerexw

@tannergooding
Copy link
Member

tannergooding commented Aug 25, 2022

if (!Thread.Yield())
Thread.Sleep(0);

You typically don't want to use Thread.Yield() or Thread.Sleep(0), both relinquish the remainder of the allocated time slice and can cause you to not run again until the OS schedule first goes through the queue of equal and higher priority threads waiting to run.

The pause (x86/x64) and yield (Arm32/Arm64) instructions are different from these.

This is also why in games you may not want to use the various built-in System.Threading.* locking primitives (like SpinLock) as these frequently utilize Yield or Sleep under the hood. Lower level graphics frameworks (like DX12MA) frequently use SRWLOCK (Windows) instead, as that provides the relevant behavior without incurring the negative side effects of relinquishing the time slice and thus is suitable for use in such games or other multimedia based applications.

@KSemenenko
Copy link
Author

Yes, also time for call back excecution.
It’s timer, so it’s generate event and don’t wait until previous one will be finished

@KSemenenko
Copy link
Author

So, only windows api is the blocker?

@tannergooding
Copy link
Member

This is not just a Windows limitation, this is a limitation of both hardware and software for almost any platform that might be targeted.

Most platforms are not designed or oriented around real time execution, targeting sub millisecond execution is not something that really has any guarantees for most platforms and which often requires additional care and work to make partially possible in the first place

@Donis-
Copy link

Donis- commented Aug 29, 2022

NtSetTimerResolution is undocumented -> we cannot use it.

I'm not advocating that the runtime uses these APIs to change system timers.
Undocumented NtSetTimerResolution or documented timeBeginPeriod and timeEndPeriod can be used by the user via pinvoke.

What the user cannot change is the utilization of low resolution GetTickCount / GetTickCount64 in runtime system timers implementation, hence the ask if there could be a use of QueryPerformanceCounter timers, for example:

@tannergooding
Copy link
Member

There are many more considerations than simply increasing the timer resolution or utilizing QPC within a timer.

This is something that is going to be application specific and which needs to be considered at the very least thread wide for a given process, but more likely also managed even higher at the general process level.

It is not something which can be trivially provided by the BCL. The BCL does, however, already expose all the relevant tools such that an interested user could roll their own scenario specific timer after having invoked the relevant platform specific APIs.

@Slendermid
Copy link

hello!
if you use timer call from driver (ExSetTimerResolution) all works good.
Only 1 programm use this timer call is DPC LATENCY CHEKER. just open it and this function is start working.

but i have a question. My platform z790 have a 3 timer resoltion in TSC tick.
0.997 0.999 1000
im want use only 1000, but im tired reboot to reboot to get this value. Im want stable 1ms in my system, and i dont want use RTC tick (useplatformtick yes) to get 1ms.
how to get stable 1000us (not 0.997, 0.999 or like that) on TSC tick?

@Donis-
Copy link

Donis- commented May 24, 2023

@Slendermid sounds like it could be related to "clock spread spectrum in the BIOS settings", though not sure.
Try to disable it and retest.

@Slendermid
Copy link

@Slendermid sounds like it could be related to "clock spread spectrum in the BIOS settings", though not sure. Try to disable it and retest.

im thinking about that, because all z690/z790 on ddr4 memory dont have option to disable bclk spread spectrum, but i have a friend with z790 ddr5 board, and same values.
Also, base cpu speed is 3.42 not 3.40 like on am4/z390/z490, but if im enable EIST it will be 3.40, anyway, that not resolve problem.
im try to write a driver with 1001 timer res, it give me stable 1000, but after open another program, it decrease to 0.997.
Maybe have any function to stable lock timer to 1000us?

@Clockwork-Muse
Copy link
Contributor

write a driver

You're trying to write a driver in C#? Good luck with that - you'd be better off with C/C++ or Rust.

Maybe have any function to stable lock timer to 1000us?

This is an OS/hardware layer concern, and C# doesn't natively provide a way to even get a timer at that resolution, and so we're unlikely to be able to provide something like that in the first place.
I'm not sure such a thing would even actually be provided (at least not on standard consumer hardware/OS) - your driver/whatever is normally expected to handle the actual delta that occurred, not assume it was completely static.

@Slendermid
Copy link

my code in driver: (also im not a good coder)

#include <ntddk.h>
NTSTATUS DriverEntry()
{
ExSetTimerResolution(10000, TRUE);
return STATUS_SUCCESS;
}

i know is hardware issue or like that, but if system can get 1000 or 0.9999 randomly (instead of 0.9966) i think we can lock it on 1000. If the system has chosen 0.9966 timer res i can stable it to 1000, but if another programm call timer (1ms), it decrese to 0.9966. Im prefer 0.9999 or 1000 because that values have lowest Kernel Timer Latency. (1-3us vs ~950us). Also RTC tick with always stable 1000 timer res give me 1000us kernel latency.

@azhmur
Copy link
Contributor

azhmur commented Oct 5, 2024

Some MS guys just did it for GO on Windows: https://devblogs.microsoft.com/go/high-resolution-timers-windows/
(And it seems it was working for GO on linux for a long time)
I suppose Net can have similar approach implemented as well.

@KSemenenko
Copy link
Author

So maybe in .NET 10 then ? 😉

@tannergooding
Copy link
Member

As per the above, there is a "lot" of complexity in doing this and doing it correctly. It's also not something that can be strictly guaranteed across all systems/hardware and the places it is needed are a bit more niche.

Due to all of that, this isn't something I can see getting prioritized for .NET 10. However, there is nothing preventing a 3rd party library from being created that provides the functionality in the meantime.

@Uight
Copy link

Uight commented Feb 25, 2025

I did check the approach from https://devblogs.microsoft.com/go/high-resolution-timers-windows/
in a little test project i did: https://github.com/Uight/QuickTick
Basically it works and seems to even be superior to using timeSetEvent winmm api or the newer CreateTimerQueueTimer together with timeBeginPeriod to get sub 15.6ms resolution.
However i can only agree to things said before in this issue. Doing a right implementation is hard and with scheduling/priority problems and windows not being a Realtime-System you can create a timer like this but you shouldnt expect it to really precise.
E.g. from my tests you regularly get a peak in the interval sometimes even up to 30ms altough setting the timer to 1ms. The only thing you can get with timers like this is that you can achieve an average interval of below 15.6ms but dont try to get a precise 1ms timing because when you set that youll either get zero times or over 1.5ms with an average of 1ms...
Also power saving mode seems to play a big role. With this particular implementation completly stopping when your in power saving mode.
Another factor as stated above is the cpu or the general system setup. Some of my findings:

  1. Win10 seems to be more precise than Win11
  2. If you use WinDefender for business and dont have an exclude on the particular service timing is completly unpredictible
  3. PCs that can not boost (mostly industry PC with a set cpu clock speed) are way more precise and stable

This isnt really about .net but more about windows. In dotnet you can get pretty much perfect timers if you use .net8.0 and run it on linux and use one of the mainLine Linux kernels which have the rt patch fully integrated since end of last year. (Provided your cpu has plenty of headroom)

Small update:
It might be a dotnet runtime thing after all. Because you can run the very same code in c++ directily and you get a very very stable timer down to 1ms without a problem. Im not sure exactly how windows or the dotnet runtime handles the the thread that is waiting for GetQueuedCompletionStatus. In c++ that thread can instantly be signaled. giving high timer precision but in .net on the same system this can take significant time to get the thread to be signaled/run. This is evident because you can increase the threads priority and that can increase the timers accurancy greatly. On linux you also dont seem to have that problem (other timer of course) as the dotnet runtime doesnt seem to have the same scheduling overhead as on windows)

@KSemenenko
Copy link
Author

Wow, thanks for this amazing info!

@Uight
Copy link

Uight commented Feb 27, 2025

I now did clean the code a bit and created a semi clean version which is also on nuget: https://www.nuget.org/packages/QuickTickLib/
if you wanna test it or modify the code feel free to do so.
Maybe theres more to get from this approach. But im definitly not an expert in Windows Api calls or threading/scheduling.

Just be aware that for the timer the event code is currently executed within the internal timing loop as i couldnt get a clean solution for that. The timer is set up in a way that it holds the interval provided as an average and is normaly accurate to within 1ms. Sleeping or delaying for one 1ms typically is more around 1.5ms which i guess is way better than 15.6ms
Could be a valid approach to get better timers on windows. But for me coming from realtime systems i find it sad that i cant get a accurate timing to lets say 200µs on a multi gigahertz system with .net on windows ;)

Update (April 12th 2025): cleanup up that package so its pretty much safe to use now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests