Created
March 6, 2020 01:10
-
-
Save michel-pi/ce7e612bff16c0b9f51959059a38b637 to your computer and use it in GitHub Desktop.
Windows timer resolution and high accuracy Sleep
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Diagnostics; | |
using System.Runtime.InteropServices; | |
using System.Threading; | |
namespace Testing | |
{ | |
internal static class StopwatchExtensions | |
{ | |
public static long GetElapsedNanoseconds(this Stopwatch watch) | |
{ | |
return TimerConverter.TicksToNanoseconds(watch.ElapsedTicks); | |
} | |
} | |
internal static class TimerConverter | |
{ | |
public static long Accuracy => 1000000000L / Stopwatch.Frequency; | |
public static long MillisecondsToNanoseconds(int milliseconds) | |
{ | |
return milliseconds * 1000000L; | |
} | |
public static int NanosecondsToMilliseconds(long nanoseconds) | |
{ | |
return unchecked((int)(nanoseconds / 1000000.0D)); | |
} | |
public static long TicksToMilliseconds(long ticks) | |
{ | |
return (long)(ticks / (double)Stopwatch.Frequency * 1000.0D); | |
} | |
public static double TicksToMillisecondsLossless(long ticks) | |
{ | |
return (ticks / (double)Stopwatch.Frequency) * 1000.0D; | |
} | |
public static long TicksToNanoseconds(long ticks) | |
{ | |
return (long)(ticks / (double)Stopwatch.Frequency * 1000000000.0D); | |
} | |
public static long TicksToSeconds(long ticks) | |
{ | |
return (long)(ticks / (double)Stopwatch.Frequency); | |
} | |
} | |
public static class TimerService | |
{ | |
public static long Accuracy { get; } = Stopwatch.Frequency; | |
public static int ClockResolution { get; } = GetClockResolution(); | |
public static long ClockResolutionNanoseconds { get; } = TimerConverter.MillisecondsToNanoseconds(ClockResolution); | |
private static int GetClockResolution() | |
{ | |
var timecaps = new TimeCaps(); | |
var result = TimeGetDevCaps(ref timecaps, sizeof(uint) * 2); | |
return result == MultimediaResult.NoError | |
? (int)timecaps.PeriodMin | |
: 15; // 15ms is the default timer resolution on windows | |
} | |
private static void SpinLockSleep(long nanoseconds) | |
{ | |
var watch = Stopwatch.StartNew(); | |
while (watch.GetElapsedNanoseconds() < nanoseconds) | |
{ | |
// do nothing | |
} | |
} | |
[DllImport("Winmm.dll", EntryPoint = "timeGetDevCaps")] | |
private static extern MultimediaResult TimeGetDevCaps([In, Out] ref TimeCaps timeCaps, uint timeCapsSize); | |
private static void YieldSleep(long nanoseconds) | |
{ | |
var watch = Stopwatch.StartNew(); | |
while (watch.GetElapsedNanoseconds() < nanoseconds) | |
{ | |
Thread.Yield(); | |
} | |
} | |
public static void Sleep(int milliseconds) | |
{ | |
if (ClockResolution == 1 || ClockResolution == milliseconds) | |
{ | |
Thread.Sleep(milliseconds); | |
} | |
else if (milliseconds < ClockResolution) | |
{ | |
YieldSleep(TimerConverter.MillisecondsToNanoseconds(milliseconds)); | |
} | |
else | |
{ | |
int count = milliseconds / ClockResolution; | |
int remainder = milliseconds % ClockResolution; | |
for (int i = 0; i < count; i++) | |
{ | |
Thread.Sleep(ClockResolution); | |
} | |
YieldSleep(TimerConverter.MillisecondsToNanoseconds(remainder)); | |
} | |
} | |
public static void SleepHighResolution(long nanoseconds) | |
{ | |
if (nanoseconds == ClockResolutionNanoseconds) | |
{ | |
Thread.Sleep(ClockResolution); | |
} | |
else if (nanoseconds < ClockResolutionNanoseconds) | |
{ | |
YieldSleep(nanoseconds); | |
} | |
else | |
{ | |
long count = nanoseconds / ClockResolutionNanoseconds; | |
long remainder = nanoseconds % ClockResolutionNanoseconds; | |
for (long i = 0L; i < count; i++) | |
{ | |
Thread.Sleep(ClockResolution); | |
} | |
YieldSleep(remainder); | |
} | |
} | |
public static void WaitHighResolution(long nanoseconds) | |
{ | |
if (nanoseconds == ClockResolutionNanoseconds) | |
{ | |
Thread.Sleep(ClockResolution); | |
} | |
else if (nanoseconds < ClockResolutionNanoseconds) | |
{ | |
SpinLockSleep(nanoseconds); | |
} | |
else | |
{ | |
long count = nanoseconds / ClockResolutionNanoseconds; | |
long remainder = nanoseconds % ClockResolutionNanoseconds; | |
for (long i = 0L; i < count; i++) | |
{ | |
Thread.Sleep(ClockResolution); | |
} | |
SpinLockSleep(remainder); | |
} | |
} | |
/* | |
* Note: | |
* Possibly cache the used stopwatch with a ThreadStatic variable to safe memory allocations. | |
*/ | |
} | |
public enum MultimediaResult : uint | |
{ | |
NoError = 0, | |
Error = 1, | |
BadDeviceId = 2, | |
NotEnabled = 3, | |
Allocated = 4, | |
InvalidHandle = 5, | |
NoDriver = 6, | |
NoMemory = 7, | |
NotSupported = 8, | |
BadErrorNumber = 9, | |
InvalidFlag = 10, | |
InvalidParameter = 11, | |
HandleBusy = 12, | |
InvalidAlias = 13, | |
BadDatabase = 14, | |
KeyNotFound = 15, | |
ReadError = 16, | |
WriteError = 17, | |
DeleteError = 18, | |
ValueNotFound = 19, | |
NoDriverCB = 20, | |
BadFormat = 32, | |
StillPlaying = 33, | |
Unprepared = 34 | |
} | |
[StructLayout(LayoutKind.Sequential)] | |
public struct TimeCaps | |
{ | |
public uint PeriodMin; | |
public uint PeriodMax; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment