Skip to content

Commit 27f04f7

Browse files
johnwasonmergify[bot]
authored andcommitted
Use high resolution timer on Windows (#3478)
This PR fixes timing issues on Windows. Simulation ratios have been observed as low as 6.5% on Windows with an empty scene, and simulation rates often hover around 80% with busier scenes. The problem was caused by the loop rate stabilization strategy. By default the Windows kernel timing resolution is 15.6 ms, meaning that a normal sleep command can not wake before 15.6 ms. On older version of Windows, a system-wide command was used to modify the system timing resolution, meaning that if any process requested a higher timing resolution all timers would also have this resolution. Newer Windows updates have changed this behavior, and each high resolution timer must now use the CREATE_WAITABLE_TIMER_HIGH_RESOLUTION with the Win32 API function CreateWaitableTimerEx() to create a timer that has the higher resolution capability. This PR modifies the SimulationRunner class to use a high resolution timer instead of std::this_thread::sleep_until() when on Windows. --------- Signed-off-by: John Wason <wason@wasontech.com> (cherry picked from commit 503bc6b) # Conflicts: # src/SimulationRunner.hh
1 parent fd59484 commit 27f04f7

2 files changed

Lines changed: 102 additions & 3 deletions

File tree

src/SimulationRunner.cc

Lines changed: 82 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,16 @@
6565
#include "LevelManager.hh"
6666
#include "SdfGenerator.hh"
6767

68+
#ifdef _WIN32
69+
#ifndef NOMINMAX
70+
#define NOMINMAX
71+
#endif
72+
#ifndef WIN32_LEAN_AND_MEAN
73+
#define WIN32_LEAN_AND_MEAN
74+
#endif
75+
#include <windows.h>
76+
#endif
77+
6878
using namespace gz;
6979
using namespace sim;
7080

@@ -101,6 +111,34 @@ struct MaybeGilScopedRelease
101111
#endif
102112
}
103113

114+
#ifdef _WIN32
115+
namespace gz
116+
{
117+
namespace sim
118+
{
119+
inline namespace GZ_SIM_VERSION_NAMESPACE {
120+
// Utility class to store the windows HANDLE variable and close
121+
// the handle using RAII. This class also hides the HANDLE
122+
// type from the global header files.
123+
class SimulationRunnerWinHandleStorage
124+
{
125+
private: HANDLE handleStorage{NULL};
126+
127+
public: HANDLE handle() { return handleStorage; }
128+
129+
public: SimulationRunnerWinHandleStorage(HANDLE h) : handleStorage(h) {}
130+
131+
public: ~SimulationRunnerWinHandleStorage() {
132+
if (handleStorage != NULL)
133+
{
134+
CloseHandle(handleStorage);
135+
}
136+
}
137+
};
138+
}
139+
}
140+
}
141+
#endif
104142

105143
//////////////////////////////////////////////////
106144
SimulationRunner::SimulationRunner(const sdf::World &_world,
@@ -179,6 +217,16 @@ SimulationRunner::SimulationRunner(const sdf::World &_world,
179217
);
180218
this->currentInfo.simTime = this->simTimeEpoch;
181219

220+
#ifdef _WIN32
221+
HANDLE winPrecisionTimerHandle = CreateWaitableTimerExA(NULL, NULL,
222+
CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS);
223+
if (winPrecisionTimerHandle != NULL)
224+
{
225+
winPrecisionTimer = std::make_unique<SimulationRunnerWinHandleStorage>(
226+
winPrecisionTimerHandle);
227+
}
228+
#endif
229+
182230
// World control
183231
transport::NodeOptions opts;
184232
std::string ns{"/world/" + this->worldName};
@@ -850,14 +898,45 @@ bool SimulationRunner::Run(const uint64_t _iterations)
850898
// larger than the typical OS + CPU C-state latency.
851899
constexpr auto kSpinThreshold = 200us;
852900

901+
auto now = std::chrono::steady_clock::now();
902+
853903
// If the scheduled update time is in the future...
854-
if (nextUpdateTime > std::chrono::steady_clock::now())
904+
if (nextUpdateTime > now)
855905
{
856906
// ...sleep until we are close to the target time.
857907
auto sleepTarget = nextUpdateTime - kSpinThreshold;
858-
if (sleepTarget > std::chrono::steady_clock::now())
908+
if (sleepTarget > now)
859909
{
910+
#ifndef _WIN32
860911
std::this_thread::sleep_until(sleepTarget);
912+
#else
913+
if (winPrecisionTimer)
914+
{
915+
auto sleepTargetDuration =
916+
std::chrono::duration_cast<std::chrono::microseconds>(
917+
sleepTarget - now);
918+
LARGE_INTEGER due_time;
919+
memset(&due_time, 0, sizeof(due_time));
920+
// Positive durations are absolute, while negative durations
921+
// are relative in 10 us intervals.
922+
// The absolute time uses the non-precision system clock so we
923+
// need to use relative time.
924+
due_time.QuadPart = -sleepTargetDuration.count() * 10;
925+
if (SetWaitableTimer(winPrecisionTimer->handle(), &due_time, 0,
926+
NULL, NULL, FALSE) != TRUE)
927+
{
928+
gzerr << "Could not SetWaitableTimer" << std::endl;
929+
}
930+
else
931+
{
932+
WaitForSingleObject(winPrecisionTimer->handle(), INFINITE);
933+
}
934+
}
935+
else
936+
{
937+
std::this_thread::sleep_until(sleepTarget);
938+
}
939+
#endif
861940
}
862941

863942
// ...then busy-wait for the final moments for precision.
@@ -868,7 +947,7 @@ bool SimulationRunner::Run(const uint64_t _iterations)
868947
}
869948

870949
// Schedule the next update time.
871-
auto now = std::chrono::steady_clock::now();
950+
now = std::chrono::steady_clock::now();
872951
nextUpdateTime += this->updatePeriod;
873952
if (nextUpdateTime < now)
874953
{

src/SimulationRunner.hh

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ namespace gz
6969
inline namespace GZ_SIM_VERSION_NAMESPACE {
7070
// Forward declarations.
7171
class SimulationRunnerPrivate;
72+
#ifdef _WIN32
73+
class SimulationRunnerWinHandleStorage;
74+
#endif
7275

7376
class GZ_SIM_VISIBLE SimulationRunner
7477
{
@@ -541,6 +544,23 @@ namespace gz
541544
private: bool threadsNeedCleanUp{false};
542545

543546
private: bool resetInitiated{false};
547+
<<<<<<< HEAD
548+
=======
549+
550+
/// \brief True to create entities.
551+
private: bool createEntities{false};
552+
private: bool entitiesCreated{false};
553+
554+
/// \brief Flag indicating if the server encountered errors during
555+
/// initialization and should exit immediately. See
556+
/// `SetExitedWithErrors()`.
557+
private: bool exitedWithErrors{false};
558+
#ifdef _WIN32
559+
private: std::unique_ptr<SimulationRunnerWinHandleStorage>
560+
winPrecisionTimer;
561+
#endif
562+
563+
>>>>>>> 503bc6b0 (Use high resolution timer on Windows (#3478))
544564
friend class LevelManager;
545565
};
546566
}

0 commit comments

Comments
 (0)