Skip to content

Instantly share code, notes, and snippets.

@tcantenot
Last active September 12, 2025 12:05
Show Gist options
  • Select an option

  • Save tcantenot/47e211e0e8dce95a33201411ead440e4 to your computer and use it in GitHub Desktop.

Select an option

Save tcantenot/47e211e0e8dce95a33201411ead440e4 to your computer and use it in GitHub Desktop.
Precise sleep function on Windows written in C using high resolution timers
#include "precise_sleep.h"
int main()
{
const int use_nt_set_timer_resolution = 1;
InitPreciseSleepFunction(use_nt_set_timer_resolution);
// Your app code that uses PreciseSleep(sleep_duration_s);
DeinitPreciseSleepFunction();
return 0;
}
#include "precise_sleep.h"
#include <Windows.h>
#pragma comment(lib, "Winmm.lib") // timeGetDevCaps, timeBeginPeriod
#ifndef K_ENABLE_PRECISE_SLEEP_LOGGING
#define K_ENABLE_PRECISE_SLEEP_LOGGING 0 // Enable/Disable logging
#endif
#if K_ENABLE_PRECISE_SLEEP_LOGGING
#include <stdio.h>
#define K_PRECISE_SLEEP_LOG(...) printf(__VA_ARGS__)
#define K_PRECISE_SLEEP_ERR(...) fprintf(stderr, __VA_ARGS__)
#else
#define K_PRECISE_SLEEP_LOG(...) (void)sizeof(0, __VA_ARGS__)
#define K_PRECISE_SLEEP_ERR(...) (void)sizeof(0, __VA_ARGS__)
#endif
// Performance-counter frequency in "counts/second"
static INT64 g_performance_counter_frequency;
// Scheduler period in ms
static double g_scheduler_period_ms;
// Windows high resolution timer
static HANDLE g_high_resolution_timer;
// Max sleep time we perform to keep high precision (in 100ns)
static INT64 g_high_resolution_timer_max_sleep_time_100ns;
// Conversion factor between performance counter and '100ns'
static double g_pc_to_100ns;
// Tolerance to avoid overshooting due to timer setup overhead (in "counts/second")
static INT64 g_high_resolution_timer_tolerance_pc;
// Tolerance to avoid overshooting due to timer setup overhead (in 100ns)
static INT64 g_high_resolution_timer_tolerance_100ns;
// Tells whether we successfully set the scheduler period via NtSetTimerResolution
static BOOLEAN g_is_using_nt_timer_resolution = FALSE;
// Previous timer resolution returned by NtQueryTimerResolution
static ULONG g_prev_timer_resolution_100ns = 0;
// High precision sleep function using Windows high resolution timer
void PreciseSleep_HighResolutionTimer(double seconds)
{
LARGE_INTEGER pc;
QueryPerformanceCounter(&pc);
const INT64 target_pc = (INT64)(pc.QuadPart + seconds * g_performance_counter_frequency);
INT64 remaining_sleep_time_pc = target_pc - pc.QuadPart;
while(remaining_sleep_time_pc > g_high_resolution_timer_tolerance_pc)
{
INT64 remaining_sleep_time_100ns = (INT64)(remaining_sleep_time_pc * g_pc_to_100ns - g_high_resolution_timer_tolerance_100ns);
// Split the sleep time in intervals of time representing a fraction of the scheduler period to avoid oversleep
if(remaining_sleep_time_100ns > g_high_resolution_timer_max_sleep_time_100ns)
remaining_sleep_time_100ns = g_high_resolution_timer_max_sleep_time_100ns;
// SetWaitableTimerEx expected a due_time_100ns time multiple of 100ns:
// - positive values indicate absolute time.
// - negative values indicate relative time.
// https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-setwaitabletimer
LARGE_INTEGER due_time_100ns;
due_time_100ns.QuadPart = -remaining_sleep_time_100ns;
SetWaitableTimerEx(g_high_resolution_timer, &due_time_100ns, 0, NULL, NULL, NULL, 0);
WaitForSingleObject(g_high_resolution_timer, INFINITE);
QueryPerformanceCounter(&pc);
remaining_sleep_time_pc = target_pc - pc.QuadPart;
}
while(pc.QuadPart < target_pc) // Spin for any remaining time
{
YieldProcessor();
QueryPerformanceCounter(&pc);
}
}
// High precision sleep function using the system Sleep
void PreciseSleep_SystemSleep(double seconds)
{
LARGE_INTEGER pc;
QueryPerformanceCounter(&pc);
const INT64 target_pc = (INT64)(pc.QuadPart + seconds * g_performance_counter_frequency);
const double k_sleep_tolerance_s = 0.000'02;
const double sleep_duration_ms = (seconds - k_sleep_tolerance_s) * 1000.0 - g_scheduler_period_ms; // Sleep for 1 scheduler period less than requested.
const int num_sleep_slices = (int)(sleep_duration_ms / g_scheduler_period_ms);
if(num_sleep_slices > 0)
Sleep((DWORD)(num_sleep_slices * g_scheduler_period_ms));
QueryPerformanceCounter(&pc);
while(pc.QuadPart < target_pc) // Spin for any remaining time
{
YieldProcessor();
QueryPerformanceCounter(&pc);
}
}
#ifdef __cplusplus
extern "C" {
#endif
// Let the linker import the functions
NTSYSAPI NTSTATUS NTAPI NtSetTimerResolution(ULONG DesiredResolution, BOOLEAN SetResolution, PULONG CurrentResolution);
NTSYSAPI NTSTATUS NTAPI NtQueryTimerResolution(PULONG MinimumResolution, PULONG MaximumResolution, PULONG CurrentResolution);
#ifdef __cplusplus
}
#endif
#pragma comment(lib, "ntdll.lib")
// Precise sleep function
void(*PreciseSleep)(double seconds) = NULL;
void InitPreciseSleepFunction(int use_nt_set_timer_resolution)
{
g_is_using_nt_timer_resolution = FALSE;
// First try to set a potentially more precise scheduler period via (the undocumented) NtSetTimerResolution
// http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/Time/NtQueryTimerResolution.html
// http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/Time/NtSetTimerResolution.html
if(use_nt_set_timer_resolution)
{
ULONG min_timer_resolution_100ns = 0;
ULONG max_timer_resolution_100ns = 0;
ULONG curr_timer_resolution_100ns = 0;
if(NtQueryTimerResolution(&min_timer_resolution_100ns, &max_timer_resolution_100ns, &curr_timer_resolution_100ns) == 0)
{
K_PRECISE_SLEEP_LOG("NtQueryTimerResolution: current resolution = %lu | range = [%lu, %lu]\n", curr_timer_resolution_100ns, max_timer_resolution_100ns, min_timer_resolution_100ns);
ULONG actual_timer_resolution_100ns;
if(NtSetTimerResolution(max_timer_resolution_100ns, TRUE, &actual_timer_resolution_100ns) == 0)
{
const double curr_timer_resolution_ms = actual_timer_resolution_100ns / 10'000.0;
g_scheduler_period_ms = curr_timer_resolution_ms;
g_is_using_nt_timer_resolution = TRUE;
}
else
{
K_PRECISE_SLEEP_ERR("NtSetTimerResolution failed.\n");
}
}
else
{
K_PRECISE_SLEEP_ERR("NtQueryTimerResolution failed.\n");
}
}
if(!g_is_using_nt_timer_resolution)
{
// https://learn.microsoft.com/en-us/windows/win32/api/timeapi/nf-timeapi-timegetdevcaps
TIMECAPS caps;
MMRESULT r = timeGetDevCaps(&caps, sizeof caps);
if(r != TIMERR_NOERROR)
K_PRECISE_SLEEP_ERR("timeGetDevCaps failed with error %u\n", r);
g_scheduler_period_ms = caps.wPeriodMin;
K_PRECISE_SLEEP_LOG("InitPreciseSleepFunction: timeBeginPeriod(%f)\n", g_scheduler_period_ms);
r = timeBeginPeriod((UINT)g_scheduler_period_ms);
if(r != TIMERR_NOERROR)
K_PRECISE_SLEEP_ERR("timeBeginPeriod(%f) failed with error %u\n", g_scheduler_period_ms, r);
}
K_PRECISE_SLEEP_LOG("Scheduler period = %fms\n", g_scheduler_period_ms);
LARGE_INTEGER qpf;
QueryPerformanceFrequency(&qpf);
g_performance_counter_frequency = qpf.QuadPart;
// 'Performance counter' -> '100ns' conversion factor
g_pc_to_100ns = 10'000'000.0 / g_performance_counter_frequency;
g_high_resolution_timer = CreateWaitableTimerExW(NULL, NULL, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS);
const double high_resolution_timer_tolerance_s = (g_scheduler_period_ms + 0.02) / 1000.0;
g_high_resolution_timer_tolerance_100ns = (INT64)(high_resolution_timer_tolerance_s * 10'000'000);
g_high_resolution_timer_tolerance_pc = (INT64)(high_resolution_timer_tolerance_s * g_performance_counter_frequency);
// Split the sleep time in intervals of time representing 95% of the scheduler period
// > "High resolution timer has a quirk that if you request a sleep period longer than
// the system timer period, the precision of the timer plummets."
// (https://blog.bearcats.nl/perfect-sleep-function/)
g_high_resolution_timer_max_sleep_time_100ns = (INT64)g_scheduler_period_ms * 9'500; // 0.95 * ms_to_100ns = 0.95 * 10'000
if(g_high_resolution_timer)
PreciseSleep = PreciseSleep_HighResolutionTimer;
else
PreciseSleep = PreciseSleep_SystemSleep;
}
void DeinitPreciseSleepFunction()
{
if(g_is_using_nt_timer_resolution)
{
ULONG actual_timer_resolution_100ns;
if(NtSetTimerResolution(g_prev_timer_resolution_100ns, TRUE, &actual_timer_resolution_100ns) != 0)
{
K_PRECISE_SLEEP_ERR("NtSetTimerResolution failed.\n");
}
}
else
{
// https://learn.microsoft.com/en-us/windows/win32/api/timeapi/nf-timeapi-timebeginperiod (in 'Remark' section)
// Call this function immediately before using timer services, and call the timeEndPeriod function immediately after you are finished using the timer services.
// You must match each call to timeBeginPeriod with a call to timeEndPeriod, specifying the same minimum resolution in both calls.
K_PRECISE_SLEEP_LOG("DeinitPreciseSleepFunction: timeEndPeriod(%f)\n", g_scheduler_period_ms);
const MMRESULT r = timeEndPeriod((UINT)g_scheduler_period_ms);
if(r != TIMERR_NOERROR)
K_PRECISE_SLEEP_ERR("timeEndPeriod(%f) failed with error %u\n", g_scheduler_period_ms, r);
}
}
#ifndef PRECISE_SLEEP_H
#define PRECISE_SLEEP_H
extern void(*PreciseSleep)(double seconds);
void InitPreciseSleepFunction(int use_nt_set_timer_resolution);
void DeinitPreciseSleepFunction();
#endif //PRECISE_SLEEP_H
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment