mercredi 30 octobre 2019

Explanation why std::this_thread::sleep_for() sleep time differs between MSVC and MinGW-GCC?

Identifying the Problem

I was busy editing a library for lua bindings to rtmidi. I wanted to fix MinGW-GCC and LLVM/Clang compilation compability. When I was done making the edits and compiling the bindings, I noticed a weird timing issue caused by std::this_thread::sleep_for() when compared to MSVC.

I understand that there are bound to be some scheduling differences between different compilers, but in the following examples you can hear large timing issues:

  • MIDI playback using MSVC compiled bindings
  • MIDI playback using GCC compiled bindings

I have narrowed it down that this is the piece of code in question:

lua_pushliteral(L, "sleep");
lua_pushcfunction(L, [] (lua_State *L) {
    auto s = std::chrono::duration<lua_Number>(luaL_checknumber(L, 1));
    std::this_thread::sleep_for(s);
    return 0;
});
lua_rawset(L, -3);

Obviously it's about these two lines:

auto s = std::chrono::duration<lua_Number>(luaL_checknumber(L, 1));
std::this_thread::sleep_for(s);

The average waiting time that is passed to sleep_for() is around 0.01s, with some calls here and there between 0.002s - 0.005s.

Troubleshooting

First off I have checked whether the problem was present with my current version of GCC (9.2.0) by using a different version and even using LLVM/Clang. Both GCC 8.1.0 and LLVM/Clang 9.0.0 yield the same results.

At this point I can conclude there is some weird scheduling going on with the winpthreads runtime, since they depend on it and MSVC does not.

After that I tried to switch out the code with the Windows Sleep() call. I had to multiply by 1000 to adjust for the correct timing.

Sleep(luaL_checknumber(L, 1) * 1000);

As I expected, the timing issue is not present here; this indeed confirms that winpthreads is the culprit here.

Obviously I do not want to make calls to Windows Sleep() and keep using sleep_for().

The Questions

So based on what I gathered I have the following questions:

  • Is winpthread indeed the culprit? Am I perhaps missing some compiler defines that would solve the problem?
  • If winpthreads is indeed the culprit, why are the timing differences so big?
  • If there is no compiler define 'fix', what would you recommend to do tackle the problem?

To partially answer the third question (if it may come to it), I was thinking of doing something like:

#ifdef _WIN32 && MINGW
    #include <windows.h>
#endif

...

#ifdef _WIN32 && MINGW
    Sleep(luaL_checknumber(L, 1) * 1000);
#elif _WIN32 && MSVC
    auto s = std::chrono::duration<lua_Number>(luaL_checknumber(L, 1));
    std::this_thread::sleep_for(s);
#endif

Of course the problem arises that Window's Sleep() call is less precise (or so I've read).

Aucun commentaire:

Enregistrer un commentaire