mardi 21 juillet 2020

How to put a thread to sleep for microseconds? (invalid implementation for sleep_for in MinGW)

Since std::chrono::sleep_for accepts microseconds, I expected it to be able to sleep for microseconds with some reasonable bias (sleep a little longer, definitely not shorter).

However, it turned out that I was wrong and it seems to be not possible with STL. MSVC implementation of sleep_for makes the thread to sleep for a few milliseconds (!!!). MinGW implementation bluntly ignores sleep_for command for microseconds, 1 millisecond, and have been noticed several times to wake up spuriously (though such behavior is not specified by the standard).

When MinGW is told to sleep for 1 millisecond, it sleeps unreasonably longer than 1 millisecond or MSVC.

Here is a code snippet that I use for testing

#include <thread>
#include <condition_variable>
#include <chrono>

#include <iostream>
#include <locale>

struct separate_thousands : std::numpunct<char>
{
    char_type do_thousands_sep() const override { return ','; }  // separate with commas
    string_type do_grouping() const override { return "\3"; } // groups of 3 digit
};

class StopWatch
{
    std::chrono::steady_clock::time_point start_;

public:
    StopWatch()
        : start_(std::chrono::steady_clock::now())
    {}

    void Reset()
    {
        start_ = std::chrono::steady_clock::now();
    }

    std::chrono::nanoseconds Result() const
    {
        return std::chrono::steady_clock::now() - start_;
    }

    template <typename MetricPrefix>
    std::chrono::duration<long long, MetricPrefix> Result(MetricPrefix) const
    {
        return std::chrono::duration_cast<
            std::chrono::duration<long long, MetricPrefix>
        >(Result());
    }

    template <typename MetricPrefix>
    operator std::chrono::duration<long long, MetricPrefix>() const
    {
        return Result(MetricPrefix());
    }
};

template <typename DurationType>
std::chrono::nanoseconds SleepFor(DurationType duration)
{
    StopWatch sw{};
    std::this_thread::sleep_for(duration);
    return sw;
}

template <typename DurationType>
std::chrono::nanoseconds WaitFor(DurationType duration)
{
    std::mutex cvMutex{};
    std::condition_variable cv{};
    StopWatch sw{};

    std::unique_lock<std::mutex> ul{cvMutex};
    cv.wait_for(ul, std::chrono::microseconds(1));
    return sw;
}


int main()
{

    std::cout.imbue(std::locale(std::cin.getloc(), new separate_thousands()));

    StopWatch sw{};
    std::chrono::nanoseconds durationNanosec = sw;
    std::cout << "Duration between 2 \"std::chrono::steady_clock::now()\" calls: " <<
        durationNanosec.count() << " nanoseconds" << std::endl;

    std::cout << "Duration of sleep_for 1 microsecond "
        << SleepFor(std::chrono::microseconds(1)).count()
        << " nanoseconds" << std::endl;

    std::cout << "Duration of sleep_for 5 microseconds "
              << SleepFor(std::chrono::microseconds(5)).count()
              << " nanoseconds" << std::endl;

    std::cout << "Duration of sleep_for 100 microseconds "
              << SleepFor(std::chrono::microseconds(100)).count()
              << " nanoseconds" << std::endl;

    std::cout << "Duration of sleep_for 500 microseconds "
              << SleepFor(std::chrono::microseconds(500)).count()
              << " nanoseconds" << std::endl;

    std::cout << "Duration of sleep_for 1 milliseconds "
              << SleepFor(std::chrono::milliseconds(1)).count()
              << " nanoseconds" << std::endl;

    // this executes far longer than 1 second on MinGW!
    for (size_t i = 0; i != 1000; ++i)
    {
        std::chrono::nanoseconds duration = SleepFor(std::chrono::milliseconds(1));
        if (duration < std::chrono::milliseconds(1))
        {
            std::cout << "Slept less than 1ms : " << duration.count() << " nanoseconds" << std::endl;
            break;
        }
    }

    // this executes far longer than 2 seconds on MinGW!
    for (size_t i = 0; i != 1000; ++i)
    {
        std::chrono::nanoseconds duration = SleepFor(std::chrono::milliseconds(2));
        if (duration < std::chrono::milliseconds(2))
        {
            std::cout << "Slept less than 2ms : " << duration.count() << " nanoseconds" << std::endl;
            break;
        }
    }

    std::cout << "Duration of sleep_for 10 milliseconds "
              << SleepFor(std::chrono::milliseconds(10)).count()
              << " nanoseconds" << std::endl;

    std::cout << "Duration of condition_variable::wait_for 1 microsecond "
        << WaitFor(std::chrono::microseconds(1)).count()
        << " nanoseconds" << std::endl;

    std::cout << "Duration of condition_variable::wait_for 5 microseconds "
        << WaitFor(std::chrono::microseconds(5)).count()
        << " nanoseconds" << std::endl;

    std::cout << "Duration of condition_variable::wait_for 1 millisecond "
              << WaitFor(std::chrono::milliseconds(1)).count()
              << " nanoseconds" << std::endl;

    std::cout << "Duration of condition_variable::wait_for 10 milliseconds "
              << WaitFor(std::chrono::milliseconds(10)).count()
              << " nanoseconds" << std::endl;

    return 0;
}

With MinGW the output may be something like this:

Duration between 2 "std::chrono::steady_clock::now()" calls: 100 nanoseconds
Duration of sleep_for 1 microsecond 700 nanoseconds
Duration of sleep_for 5 microseconds 300 nanoseconds
Duration of sleep_for 100 microseconds 400 nanoseconds
Duration of sleep_for 500 microseconds 200 nanoseconds
Duration of sleep_for 1 milliseconds 7,956,100 nanoseconds
Slept less than 2ms : 1,839,900 nanoseconds
Duration of sleep_for 10 milliseconds 20,629,600 nanoseconds
Duration of condition_variable::wait_for 1 microsecond 6,402,400 nanoseconds
Duration of condition_variable::wait_for 5 microseconds 14,985,800 nanoseconds
Duration of condition_variable::wait_for 1 millisecond 13,803,400 nanoseconds
Duration of condition_variable::wait_for 10 milliseconds 14,900,900 nanoseconds

With MSVC:

Duration between 2 "std::chrono::steady_clock::now()" calls: 300 nanoseconds
Duration of sleep_for 1 microsecond 13,200 nanoseconds
Duration of sleep_for 5 microseconds 1,694,100 nanoseconds
Duration of sleep_for 100 microseconds 1,572,900 nanoseconds
Duration of sleep_for 500 microseconds 1,694,300 nanoseconds
Duration of sleep_for 1 milliseconds 1,645,000 nanoseconds
Duration of sleep_for 10 milliseconds 10,976,100 nanoseconds
Duration of condition_variable::wait_for 1 microsecond 46,100 nanoseconds
Duration of condition_variable::wait_for 5 microseconds 21,300 nanoseconds
Duration of condition_variable::wait_for 1 millisecond 28,700 nanoseconds
Duration of condition_variable::wait_for 10 milliseconds 22,500 nanoseconds

I am aware that I can use busy waiting to wait for nanoseconds, but this is a no go, since I do not want to waste CPU resources. Moreover, MinGW implementation seems to provide unpredictable results.

  1. Is there a way to sleep for microseconds?
  2. Is there a good crossplatform alternative for STL threads that implements sleeping for microseconds?

Aucun commentaire:

Enregistrer un commentaire