Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::this_thread::sleep_for sleeps shorter than expected in VS2015

Let's have the following piece of code, that simply measures the duration of std::this_thread::sleep_for called with 20ms:

#include <iostream>
#include <chrono>
#include <thread>

using namespace std;
using namespace std::chrono;

int main()
{
    for (int i = 0; i < 20; i++)
    {
        auto start = steady_clock::now();
        this_thread::sleep_for(milliseconds(20));
        auto end = steady_clock::now();
        duration<double, milli> elapsed = end - start;
        cout << "Waited " << elapsed.count() << " ms\n";
    }
}

When run as compiled with toolset v120 (VS2013's) I get results as expected, i.e.:

Waited 20.0026 ms
Waited 20.0025 ms
Waited 20.0025 ms
Waited 20.0026 ms
Waited 20.0025 ms
Waited 20.0025 ms
Waited 20.0026 ms
Waited 20.0025 ms
Waited 20.0025 ms
Waited 20.0026 ms

but when run with VS2015's toolset v140, results are somewhat surprising and don't respect the promise from both msdn and cppreference.com sleep_for descriptions (that sleep_for blocks the execution of the current thread for at least the specified sleep_duration). They are as following:

Waited 19.7793 ms
Waited 19.9415 ms
Waited 19.6056 ms
Waited 19.9687 ms
Waited 19.608 ms
Waited 19.589 ms
Waited 20.5435 ms
Waited 19.5669 ms
Waited 19.6802 ms
Waited 19.5381 ms

How its possible and how can I make VS2015's sleep_for to sleep at least as long as expected?

Regards, Dawid

EDIT:

As requested those are my settings and OS details:
OS:

  • Windows 7 Professional 64bit

  • Visual Studios: 2010 Ultimate, 2013 Community, 2015 Professional with Update 1

Compiler settings:

  • default settings for Win32 Console Application,

  • any of Debug and Release configurations,

  • any of x86 and x64 target platform architectures

like image 229
dawid Avatar asked Mar 02 '16 08:03

dawid


2 Answers

The sleep_for() method implementation in VS2015 already contains a loop around the Sleep() system call, so any spurious wakeups will not affect it - see _Thrd_sleep() in VC\crt\src\stl\cthread.c.


The cause of your problems is most probably the fact that sleep_for(), and sleep_until() it calls inside, are using chrono::system_clock to calculate the time to wait, but you are measuring the period using chrono::steady_clock.

Both timers might have the same precision, but not necessarily the same accuracy. Then it can happen that the system_clock is lagging a little bit, while steady_clock is already a few μs ahead, and the wait calculated using system_clock is actually shorter than requested.


In this exact case, steady_clock is implemented using QueryPerformanceCounter() (see VC\include\chrono, search for struct steady_clock), so it will be very accurate and precise, as that's the preferred Windows API to use for exact time measurements.

system_clock is implemented using GetSystemTimePreciseAsFileTime() (see VC\include\chrono, search for struct system_clock), which promises "the highest possible level of precision (<1us)" also. Alas, as the MSDN page says, this API is supported only from Windows 8 up and you are stuck with the older GetSystemTimeAsFileTime() with just ms precision on your Windows 7 machine. And that is most probably the result of your measurement errors.


Depending on your exact use case, you might deal with this in different ways of course. I'd consider just ignoring the small error, as suspending the thread and waiting for the scheduler to wake it up will not be very accurate anyway.

like image 135
Yirkha Avatar answered Nov 03 '22 16:11

Yirkha


While standard says that an implementation should use a steady_clock for the *_for timing functions, that is not required. And if a non-steady clock is used, then adjustments in the clock can result in the sleep time falling short. In such case the timing functions might not provide useful functionality as noted by the standard.

However, your results show consistent short falling, which I think could indicate behaviour that doesn't conform to the standard.

You can work around such behaviour by measuring the time yourself using the clock of your own choice and sleeping in a loop until the target time has passed.

like image 2
eerorika Avatar answered Nov 03 '22 17:11

eerorika