-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Use QueryUnbiasedInterruptTime instead of GetTickCount64 to allow more responsive Windows apps when they opt-in #122706
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
Conversation
#119437 claims that it is not the case on Linux. (#119443 was an attempt to fix that, but it is less straightforward than one would think #119443 (comment) .) |
src/libraries/Common/src/Interop/Windows/Kernel32/Interop.QueryInterruptTime.cs
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/System/Environment.Windows.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/System/Environment.Windows.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/System/Environment.Windows.cs
Outdated
Show resolved
Hide resolved
Ah, right. I seem to have gotten those mixed up when I was writing things down. So then Windows is the only one that includes I guess it becomes a question of
If we do (include sleep/hibernation), then on Windows (assuming .NET 11 still supports 2012 R2) then the fix is a bit more complex and needs to choose one of If we don't, then on Windows we can just use There's risk both ways and general inconsistency today. I do think it would be goodness to normalize here and for Windows to match more modern conventions and respect app preferences for higher frequency interrupt cadence, particularly since it maintains the status quo for apps that aren't opting to change their precision. |
Do we think that the inconsistency between Environment.TickCount64 (includes suspend time) and timeouts (does not include suspend time) is a problem worth solving? Is there a standard that APIs like this follow by default on other platforms? |
As best as I can tell, Unix isn’t including suspend time anywhere today while Windows always is, so the inconsistency here exists for TickCount64 between Windows and Linux. We then have a large number of timers and other checks (including GC) driven off TickCount64 (or the backing call for it).
I would, just going off intuition, expect most apps aren’t impacted by or even experiencing suspend/sleep events. Those that are would be more likely mobile or laptop scenarios where users are likely on Linux. I do think it’d be worth making it consistent across platforms here and think that excluding sleep time would be the “better” route, despite it being the historical Windows precedent. I would expect most users don’t factor in sleep time and would want to be explicit if they did want that, which seems to match what modern Windows is providing and what Unix provides via its various CLOCK kinds.
Nothing formal that I’m aware of. Just most docs, tutorials, and other sources pushing users towards the APIs that don’t include sleep time by default and the ones that do having less common names. ————- If we did go the route of normalizing and chose to not have TickCount64 include sleep time, then we just leave Linux and MacOS as is. We then use QueryUnbiasedInteruptTime on Windows, which solves the Server 2012 R2 questions and simplifies the lib dependency changes. We could then consider some BiasedTickCount64 or better name, if we really felt the need, so that users could get the tick count + sleep time if desired. (But I do think we could wait for feedback on that) now seems the “right time” to do such a break, if we are going to do it, given how early we are in the release cycle |
I think the current state is:
Does this match your understanding? This suggests that Windows TickCount is the outlier and the smallest delta is to fix this outlier. It is aligned with your thinking that excluding sleep time would be the "better" route.
It would be functionally equivalent to |
@jkotas, I don't think this is entirely true and rather it is a bit dependent on which flow path we do. As an example, lets look at the flow for
We have similar checks and flow for This is also an area where Win32 has itself taken breaking changes. For example,
So while in some scenarios we might not end up factoring in sleep time, other paths will end up falling back to checking |
👍, I'll update the PR here to use
There's still quite a bit of difference here, namely in that it isn't synchronized to an external time source and so isn't impacted by things like clock adjustments. However, I do agree that we would really need and want a motivating scenario as the reason to push such a thing forward. Users do have relatively trivial workarounds if they really do need such an API and it wouldn't be hard to have some common community package for it. |
…e responsive Windows apps when they opt-in
14ccd02 to
3b42f2e
Compare
src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.Windows.cs
Show resolved
Hide resolved
|
Added When you commit this breaking change:
Tagging @dotnet/compat for awareness of the breaking change. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR replaces GetTickCount64 with QueryUnbiasedInterruptTime across Windows implementations to enable higher timer responsiveness when applications opt into multimedia scenarios. Previously, Windows was restricted to 10-16ms resolution for backward compatibility, while Unix systems already used higher-resolution timers. The change also ensures consistency with Windows 8+ behavior of excluding sleep/hibernation time from timer calculations.
Key changes:
- Replaces
GetTickCount64calls withQueryUnbiasedInterruptTimein native (C/C++) and managed (C#) code - Updates P/Invoke signature to use unsafe pointers with
SuppressGCTransitionfor better performance - Simplifies
TimerQueue.Windows.csby removing Windows 8+ version check logic, asQueryUnbiasedInterruptTimeis now used unconditionally
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/native/minipal/time.c | Updated minipal_lowres_ticks() to call QueryUnbiasedInterruptTime instead of GetTickCount64 with proper unit conversion |
| src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.Windows.cs | Simplified to directly use Environment.TickCount64 by removing Windows 8+ conditional logic that previously called QueryUnbiasedInterruptTime |
| src/libraries/System.Private.CoreLib/src/System/Environment.Windows.cs | Changed TickCount64 property implementation from GetTickCount64 to QueryUnbiasedInterruptTime with unsafe pointer usage |
| src/libraries/Common/src/Interop/Windows/Kernel32/Interop.QueryUnbiasedInterruptTime.cs | Updated P/Invoke signature to use unsafe ulong* parameter with SuppressGCTransition attribute for performance |
| src/coreclr/gc/windows/gcenv.windows.cpp | Updated GetLowPrecisionTimeStamp() to call QueryUnbiasedInterruptTime instead of GetTickCount64 |
src/libraries/Common/src/Interop/Windows/Kernel32/Interop.QueryUnbiasedInterruptTime.cs
Outdated
Show resolved
Hide resolved
Yes, I agree that there are inconsistencies. I believe that the behavior one gets most of the time does not include the sleep time today. |
…yUnbiasedInterruptTime.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
|
@EgorBot -windows_x64 |
|
@EgorBot -windows_intel |
|
@EgorBo Looks like |
|
A local benchmark gives:
So it's theoretically 50% slower, but not in any kind of meaningful way. Both because its in the 1ns range but also because while There's then also the general nuance that since this is a cached value you can have up to the timer resolution in error. That is, when you call As such, I included the timings for Source was: using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Diagnostics;
using System.Runtime.InteropServices;
BenchmarkRunner.Run<Benchmarks>();
public class Benchmarks
{
[Benchmark(Baseline = true)]
public long GetTickCount64()
{
return (long)Kernel32.GetTickCount64();
}
[Benchmark]
public unsafe long QueryUnbiasedInterruptTime()
{
ulong unbiasedTime;
int result = Kernel32.QueryUnbiasedInterruptTime(&unbiasedTime);
Debug.Assert(result != 0);
return (long)(unbiasedTime / TimeSpan.TicksPerMillisecond);
}
[Benchmark]
public unsafe long QueryUnbiasedInterruptTimePrecise()
{
ulong unbiasedTime;
Kernel32.QueryUnbiasedInterruptTimePrecise(&unbiasedTime);
return (long)(unbiasedTime / TimeSpan.TicksPerMillisecond);
}
private static readonly double s_tickFrequency = (double)(TimeSpan.TicksPerSecond / TimeSpan.TicksPerMillisecond) / Stopwatch.Frequency;
[Benchmark]
public long QueryPerformanceCounter()
{
long timestamp = Stopwatch.GetTimestamp();
return (long)(timestamp * s_tickFrequency);
}
}
internal partial class Kernel32
{
[LibraryImport("Kernel32")]
[SuppressGCTransition]
public static partial ulong GetTickCount64();
[LibraryImport("Kernel32")]
[SuppressGCTransition]
public static unsafe partial int QueryUnbiasedInterruptTime(ulong* unbiasedTime);
[LibraryImport("api-ms-win-core-realtime-l1-1-1")]
[SuppressGCTransition]
public static unsafe partial void QueryUnbiasedInterruptTimePrecise(ulong* unbiasedTime);
} |
Ah, the bot didn't expect code snippets without trailing triple-ticks, not sure it's a valid markdown, but I'll handle that in the bot too. Successful run: EgorBot/runtime-utils#573 (comment) |
jkotas
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, thank you!
It will need follow up doc updates in addition to the breaking change notice.
|
Breaking change doc is here: dotnet/docs#50755 It includes the relevant callout that we also need to update the docs page for |
📋 Breaking Change Documentation RequiredCreate a breaking change issue with AI-generated content Generated by Breaking Change Documentation Tool - 2025-12-24 06:22:14 |
Unix systems (Linux and MacOS) were already using timers like
CLOCK_MONOTONIC_COARSEandCLOCK_UPTIME_RAWand so allowed for higher responsiveness.It was only Windows which was restricting itself to 10-16ms (typically 15.5ms) of responsiveness and which did not respect apps which set higher precision tick times. This was also then inconsistent with changes the OS itself made (in Win8+) to its various Wait APIs to no longer include sleep/hibernate time (bias) as part of their timeout checks.
Linux will fallback to
CLOCK_MONOTONICifCLOCK_MONOTONIC_COARSEisn't available. The non-coarse version is closer toQueryUnbiasedInterruptTimePrecisewhich is an "exact" rather than cached query.MacOS doesn't try to use
CLOCK_UPTIME_RAW_APPROXwhich is the "coarse" equivalent.