Skip to content

Instantly share code, notes, and snippets.

@scivision
Last active January 10, 2025 01:21
Show Gist options
  • Save scivision/dbbbf33c2faf5a16f31fd6d144adc314 to your computer and use it in GitHub Desktop.
Save scivision/dbbbf33c2faf5a16f31fd6d144adc314 to your computer and use it in GitHub Desktop.
nanosleep() for Visual Studio MSVC compiler on Windows

nanosleep() and clock_gettime() for Windows precision timing

The POSIX nanosleep() function is available in Linux, macOS, BSD, Unix, etc. MinGW / MSYS2 implements nanosleep for certain platforms -- x86_64, but not ARM currently. Cygwin implements nanosleep() for x86_64 and ARM. MSVC Visual Studio currently does not implement nanosleep().

nanosleep() provides relatively small precision sleep intervals for a thread. This is useful for games and hardware interfaces among other tasks. Affiliated POSIX functions include clock_gettime() and clock_getres().

Our C implementation of nanosleep(), clock_gettime(), and clock_getres() for Windows (MSVC, Intel oneAPI, MinGW, etc.) uses Waitable Timer Objects and similar elements of the Win32 API.

#if defined(WIN_NANOSLEEP)

is used instead of simply _MSC_VER because as noted above, certain MinGW platforms currently lack nanosleep(). Perhaps over time, these platforms will gain nanosleep() support, so it's a best practice to check dynamically.

API

The C11 standard timespec is used by the win32_time.h function interface. Currently, the CLOCK_MONOTONIC parameter is ignored by our implementation.

Build

cmake -Bbuild
cmake --build build

Specify the sleep time in milliseconds. For example, to sleep for 0.5 second:

build/main 500

The execution time is dependent on system overhead and system load. Accuracy on Windows is lower than other OS generally. Using in a virtual machine also affects accuracy.

Execution time is estimated on Windows PowerShell like:

Measure-Command { build/main.exe 500 }

On other shells like

time build/main 500

C++ reference

Separately, we provide a C++ example that is cross-platform, assuming C++11.

cmake_minimum_required(VERSION 3.15)
project(SleepDemo LANGUAGES C CXX)
enable_testing()
include(CheckSymbolExists)
set(CMAKE_C_STANDARD 11)
set(WIN_NANOSLEEP 0)
if(WIN32)
check_symbol_exists(nanosleep "time.h" HAVE_NANOSLEEP)
if(NOT HAVE_NANOSLEEP)
set(WIN_NANOSLEEP 1)
add_library(nanosleep OBJECT win32_time.c)
target_include_directories(nanosleep PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
endif()
endif()
add_executable(main main.c)
target_link_libraries(main PRIVATE $<$<BOOL:${WIN_NANOSLEEP}>:nanosleep>)
target_compile_definitions(main PRIVATE $<$<BOOL:${WIN_NANOSLEEP}>:WIN_NANOSLEEP>)
add_test(NAME Sleep100ms COMMAND main 100)
set_tests_properties(Sleep100ms PROPERTIES TIMEOUT 1)
# C++ demo, not related to the main program / library
add_executable(sleep_thread_cpp sleep_thread.cpp)
file(GENERATE OUTPUT .gitignore CONTENT "*")
The MIT License (MIT)
Copyright © 2025 SciVision, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// demonstration program for win32_time.c
// this program isn't necessary, but just demonstrates nanosleep() use.
// MIT License, SciVision, Inc. 2025
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#if defined(WIN_NANOSLEEP)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include "win32_time.h"
#endif
#ifndef CLOCK_MONOTONIC
#define CLOCK_MONOTONIC 0
#endif
#define G 1000000000L
#define M 1000000L
int main(int argc, char* argv[]){
printf("long: %d bytes\n", (int) sizeof(long));
printf("long long: %d bytes\n", (int) sizeof(long long));
printf("time_t: %d bytes\n", (int) sizeof(time_t));
printf("timespec tv_nsec: %d bytes\n", (int) sizeof(((struct timespec *)0)->tv_nsec));
printf("timespec tv_sec: %d bytes\n", (int) sizeof(((struct timespec *)0)->tv_sec));
struct timespec tres, treq, tic, toc;
// get clock resolution (nanoseconds)
if (clock_getres(CLOCK_MONOTONIC, &tres) != 0){
perror("clock_getres");
return EXIT_FAILURE;
}
const long clock_res = tres.tv_nsec;
long req_millisec = (argc > 1) ? atol(argv[1]) : 100;
treq.tv_sec = req_millisec / 1000;
treq.tv_nsec = (req_millisec % 1000) * M;
printf("Clock resolution: %ld nanoseconds\n", clock_res);
printf("Requested sleep time: %lld milliseconds\n", (long long) treq.tv_sec * 1000 + treq.tv_nsec / M);
if(clock_gettime(CLOCK_MONOTONIC, &tic)){
perror("clock_gettime tic");
return EXIT_FAILURE;
}
if(nanosleep(&treq, NULL) != 0){
perror("nanosleep");
return EXIT_FAILURE;
}
if(clock_gettime(CLOCK_MONOTONIC, &toc)){
perror("clock_gettime toc");
return EXIT_FAILURE;
}
if (tic.tv_nsec > toc.tv_nsec && tic.tv_sec == toc.tv_sec) {
toc.tv_nsec += G;
toc.tv_sec--;
}
long elapsed_sec = toc.tv_sec - tic.tv_sec;
long elapsed_nsec = toc.tv_nsec - tic.tv_nsec;
double elapsed_ms = elapsed_sec * 1000 + (double)elapsed_nsec / M;
if(elapsed_ms > 1000){
printf("Elapsed time: %.3f seconds\n", elapsed_ms / 1000);
} else {
printf("Elapsed time: %.3f milliseconds\n", elapsed_ms);
}
return EXIT_SUCCESS;
}
// reference to compare with win32_time.c
#include <chrono>
#include <iostream>
#include <thread>
#include <cstdlib>
int main(int argc, char* argv[])
{
using namespace std::chrono_literals;
const auto ms_req = (argc > 1) ? std::atol(argv[1]) : 100;
std::cout << "waiting for " << ms_req << " ms." << std::endl;
const auto start = std::chrono::steady_clock::now();
std::this_thread::sleep_for(std::chrono::milliseconds(ms_req));
const auto end = std::chrono::steady_clock::now();
const std::chrono::duration<double, std::milli> elapsed = end - start;
std::cout << "Waited " << elapsed.count() << " ms\n";
return EXIT_SUCCESS;
}
// nanosleep(), clock_gettime(), clock_getres() for Windows
// MIT License, SciVision, Inc. 2025
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include "win32_time.h"
#ifndef __has_c_attribute
#define __has_c_attribute(x) 0
#endif
#if __has_c_attribute(maybe_unused)
#define MAYBE_UNUSED [[maybe_unused]]
#else
#define MAYBE_UNUSED
#endif
#define G 1000000000L
int nanosleep(const struct timespec* ts, MAYBE_UNUSED struct timespec* rem){
HANDLE timer = CreateWaitableTimer(NULL, TRUE, NULL);
if(!timer)
return -1;
// SetWaitableTimer() defines interval in 100ns units.
// negative is to indicate relative time.
time_t sec = ts->tv_sec + ts->tv_nsec / G;
long nsec = ts->tv_nsec % G;
LARGE_INTEGER delay;
delay.QuadPart = -(sec * G + nsec) / 100;
BOOL ok = SetWaitableTimer(timer, &delay, 0, NULL, NULL, FALSE) &&
WaitForSingleObject(timer, INFINITE) == WAIT_OBJECT_0;
CloseHandle(timer);
if(!ok)
return -1;
return 0;
}
int clock_gettime(MAYBE_UNUSED clockid_t clk_id, struct timespec *t){
LARGE_INTEGER count;
if(!QueryPerformanceCounter(&count))
return -1;
LARGE_INTEGER freq;
if(!QueryPerformanceFrequency(&freq))
return -1;
t->tv_sec = count.QuadPart / freq.QuadPart;
t->tv_nsec = ((count.QuadPart % freq.QuadPart) * G) / freq.QuadPart;
return 0;
}
int clock_getres(MAYBE_UNUSED clockid_t clk_id, struct timespec *res){
LARGE_INTEGER freq;
if(!QueryPerformanceFrequency(&freq))
return -1;
res->tv_sec = 0;
res->tv_nsec = (G / freq.QuadPart);
return 0;
}
#include <time.h>
#ifndef clockid_t
#define clockid_t int
#endif
int nanosleep(const struct timespec*, struct timespec*);
int clock_gettime(clockid_t, struct timespec*);
int clock_getres(clockid_t, struct timespec*);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment