Skip to content

Commit 043c184

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 5da5220 commit 043c184

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
@@ -66,6 +66,16 @@
6666
#include "LevelManager.hh"
6767
#include "SdfGenerator.hh"
6868

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

@@ -103,6 +113,34 @@ struct MaybeGilScopedRelease
103113

104114
}
105115

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

107145
//////////////////////////////////////////////////
108146
SimulationRunner::SimulationRunner(const sdf::World &_world,
@@ -181,6 +219,16 @@ SimulationRunner::SimulationRunner(const sdf::World &_world,
181219
);
182220
this->currentInfo.simTime = this->simTimeEpoch;
183221

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

923+
auto now = std::chrono::steady_clock::now();
924+
875925
// If the scheduled update time is in the future...
876-
if (nextUpdateTime > std::chrono::steady_clock::now())
926+
if (nextUpdateTime > now)
877927
{
878928
// ...sleep until we are close to the target time.
879929
auto sleepTarget = nextUpdateTime - kSpinThreshold;
880-
if (sleepTarget > std::chrono::steady_clock::now())
930+
if (sleepTarget > now)
881931
{
932+
#ifndef _WIN32
882933
std::this_thread::sleep_until(sleepTarget);
934+
#else
935+
if (winPrecisionTimer)
936+
{
937+
auto sleepTargetDuration =
938+
std::chrono::duration_cast<std::chrono::microseconds>(
939+
sleepTarget - now);
940+
LARGE_INTEGER due_time;
941+
memset(&due_time, 0, sizeof(due_time));
942+
// Positive durations are absolute, while negative durations
943+
// are relative in 10 us intervals.
944+
// The absolute time uses the non-precision system clock so we
945+
// need to use relative time.
946+
due_time.QuadPart = -sleepTargetDuration.count() * 10;
947+
if (SetWaitableTimer(winPrecisionTimer->handle(), &due_time, 0,
948+
NULL, NULL, FALSE) != TRUE)
949+
{
950+
gzerr << "Could not SetWaitableTimer" << std::endl;
951+
}
952+
else
953+
{
954+
WaitForSingleObject(winPrecisionTimer->handle(), INFINITE);
955+
}
956+
}
957+
else
958+
{
959+
std::this_thread::sleep_until(sleepTarget);
960+
}
961+
#endif
883962
}
884963

885964
// ...then busy-wait for the final moments for precision.
@@ -890,7 +969,7 @@ bool SimulationRunner::Run(const uint64_t _iterations)
890969
}
891970

892971
// Schedule the next update time.
893-
auto now = std::chrono::steady_clock::now();
972+
now = std::chrono::steady_clock::now();
894973
nextUpdateTime += this->updatePeriod;
895974
if (nextUpdateTime < now)
896975
{

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)