Skip to content

Instantly share code, notes, and snippets.

@michel-pi
Created March 6, 2020 01:10
Show Gist options
  • Save michel-pi/ce7e612bff16c0b9f51959059a38b637 to your computer and use it in GitHub Desktop.
Save michel-pi/ce7e612bff16c0b9f51959059a38b637 to your computer and use it in GitHub Desktop.
Windows timer resolution and high accuracy Sleep
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