UTL

Collection of self-contained header-only libraries for C++17

View on GitHub

utl::sleep

<- to README.md

<- to implementation.hpp

sleep implements 3 variations of sleep() function with a goal of providing improved precision.

Definitions

void system(double ms);   // imprecise, uses CPU   0% of time, expected error ~0.1-5 ms
void spinlock(double ms); //   precise, uses CPU 100% of time, expected error ~0.01 ms
void hybrid(double ms);   //   precise, uses CPU  ~5% of time, expected error ~0.01 ms

Methods

sleep::system(double ms);

System sleep is an alias of std::this_thread::sleep_for(), as such it’s subjected to OS scheduler which wakes up approximately every ~3ms (usually, true value depends on the system and may vary significantly), with delays even more inconsistent for larger sleep times, which deems it unfit for such applications as real-time rendering.

sleep::spinlock(double ms);

Spinlock sleep is a common way of implementing thread locks, it is based in a looped time check, which ends the loop once timer tuns out. While such approach allows for a much greater precision it has a downside of constantly using corresponding CPU thread.

sleep::hybrid(double ms);

Hybrid version loops a short system sleep, estimating it’s error mean and variance on the fly (Welford’s algorithm), and once remaining time gets below the mean plus a standard deviation switches to spinlock. This results in a precision almost as good as that of a pure spinlock, while keeping the CPU thread mostly free.

Examples

Comparing sleep precision

[ Run this code ]

using namespace utl;

constexpr int repeats = 6;
constexpr double sleep_duration_ms = 16.67;

std::cout << "Sleeping for " << sleep_duration_ms << " ms.\n";

std::cout << "\nsleep::spinlock():\n";
for (int i = 0; i < repeats; ++i) {
    auto start = std::chrono::steady_clock::now();

    sleep::spinlock(sleep_duration_ms);

    auto end = std::chrono::steady_clock::now();
    std::cout << std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count() / 1e6 << " ms\n";
}

std::cout << "\nsleep::hybrid():\n";
for (int i = 0; i < repeats; ++i) {
    auto start = std::chrono::steady_clock::now();

    sleep::hybrid(sleep_duration_ms);

    auto end = std::chrono::steady_clock::now();
    std::cout << std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count() / 1e6 << " ms\n";
}

std::cout << "\nsleep::system():\n";
for (int i = 0; i < repeats; ++i) {
    auto start = std::chrono::steady_clock::now();

    sleep::system(sleep_duration_ms);

    auto end = std::chrono::steady_clock::now();
    std::cout << std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count() / 1e6 << " ms\n";
}

Output:

Sleeping for 16.67 ms.

sleep::spinlock():
16.6713 ms
16.6709 ms
16.6708 ms
16.6711 ms
16.6709 ms
16.6715 ms

sleep::hybrid():
16.6728 ms
16.6713 ms
16.6713 ms
16.6711 ms
16.671 ms
16.6713 ms

sleep::system():
20.0231 ms
30.344 ms
30.678 ms
30.6268 ms
30.6562 ms
30.1724 ms