Skip to content

Commit c680072

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.cc # src/SimulationRunner.hh
1 parent a352d3d commit c680072

File tree

2 files changed

+152
-3
lines changed

2 files changed

+152
-3
lines changed

src/SimulationRunner.cc

Lines changed: 126 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,84 @@
3939
#include "network/NetworkManagerPrimary.hh"
4040
#include "SdfGenerator.hh"
4141

42+
#ifdef _WIN32
43+
#ifndef NOMINMAX
44+
#define NOMINMAX
45+
#endif
46+
#ifndef WIN32_LEAN_AND_MEAN
47+
#define WIN32_LEAN_AND_MEAN
48+
#endif
49+
#include <windows.h>
50+
#endif
51+
4252
using namespace gz;
4353
using namespace gz::sim;
4454

4555
using StringSet = std::unordered_set<std::string>;
4656

57+
<<<<<<< HEAD
58+
=======
59+
namespace {
60+
#ifdef HAVE_PYBIND11
61+
// Helper struct to maybe do a scoped release of the Python GIL. There are a
62+
// number of ways where releasing and acquiring the GIL is not necessary:
63+
// 1. Gazebo is built without Pybind11
64+
// 2. The python interpreter is not initialized. This could happen in tests that
65+
// create a SimulationRunner without sim::Server where the interpreter is
66+
// initialized.
67+
// 3. sim::Server was instantiated by a Python module. In this case, there's a
68+
// chance that this would be called with the GIL already released.
69+
struct MaybeGilScopedRelease
70+
{
71+
MaybeGilScopedRelease()
72+
{
73+
if (Py_IsInitialized() != 0 && PyGILState_Check() == 1)
74+
{
75+
this->release.emplace();
76+
}
77+
}
78+
std::optional<pybind11::gil_scoped_release> release;
79+
};
80+
#else
81+
struct MaybeGilScopedRelease
82+
{
83+
// The empty constructor is needed to avoid an "unused variable" warning
84+
// when an instance of this class is used.
85+
MaybeGilScopedRelease(){}
86+
};
87+
#endif
88+
89+
}
90+
91+
#ifdef _WIN32
92+
namespace gz
93+
{
94+
namespace sim
95+
{
96+
inline namespace GZ_SIM_VERSION_NAMESPACE {
97+
// Utility class to store the windows HANDLE variable and close
98+
// the handle using RAII. This class also hides the HANDLE
99+
// type from the global header files.
100+
class SimulationRunnerWinHandleStorage
101+
{
102+
private: HANDLE handleStorage{NULL};
103+
104+
public: HANDLE handle() { return handleStorage; }
105+
106+
public: SimulationRunnerWinHandleStorage(HANDLE h) : handleStorage(h) {}
107+
108+
public: ~SimulationRunnerWinHandleStorage() {
109+
if (handleStorage != NULL)
110+
{
111+
CloseHandle(handleStorage);
112+
}
113+
}
114+
};
115+
}
116+
}
117+
}
118+
#endif
119+
>>>>>>> 503bc6b0 (Use high resolution timer on Windows (#3478))
47120

48121
//////////////////////////////////////////////////
49122
SimulationRunner::SimulationRunner(const sdf::World *_world,
@@ -117,6 +190,25 @@ SimulationRunner::SimulationRunner(const sdf::World *_world,
117190
static_cast<int>(this->stepSize.count() / this->desiredRtf));
118191
}
119192

193+
<<<<<<< HEAD
194+
=======
195+
// Epoch
196+
this->simTimeEpoch = std::chrono::round<std::chrono::nanoseconds>(
197+
std::chrono::duration<double>{_config.InitialSimTime()}
198+
);
199+
this->currentInfo.simTime = this->simTimeEpoch;
200+
201+
#ifdef _WIN32
202+
HANDLE winPrecisionTimerHandle = CreateWaitableTimerExA(NULL, NULL,
203+
CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS);
204+
if (winPrecisionTimerHandle != NULL)
205+
{
206+
winPrecisionTimer = std::make_unique<SimulationRunnerWinHandleStorage>(
207+
winPrecisionTimerHandle);
208+
}
209+
#endif
210+
211+
>>>>>>> 503bc6b0 (Use high resolution timer on Windows (#3478))
120212
// World control
121213
transport::NodeOptions opts;
122214
std::string ns{"/world/" + this->worldName};
@@ -769,14 +861,45 @@ bool SimulationRunner::Run(const uint64_t _iterations)
769861
// larger than the typical OS + CPU C-state latency.
770862
constexpr auto kSpinThreshold = 200us;
771863

864+
auto now = std::chrono::steady_clock::now();
865+
772866
// If the scheduled update time is in the future...
773-
if (nextUpdateTime > std::chrono::steady_clock::now())
867+
if (nextUpdateTime > now)
774868
{
775869
// ...sleep until we are close to the target time.
776870
auto sleepTarget = nextUpdateTime - kSpinThreshold;
777-
if (sleepTarget > std::chrono::steady_clock::now())
871+
if (sleepTarget > now)
778872
{
873+
#ifndef _WIN32
779874
std::this_thread::sleep_until(sleepTarget);
875+
#else
876+
if (winPrecisionTimer)
877+
{
878+
auto sleepTargetDuration =
879+
std::chrono::duration_cast<std::chrono::microseconds>(
880+
sleepTarget - now);
881+
LARGE_INTEGER due_time;
882+
memset(&due_time, 0, sizeof(due_time));
883+
// Positive durations are absolute, while negative durations
884+
// are relative in 10 us intervals.
885+
// The absolute time uses the non-precision system clock so we
886+
// need to use relative time.
887+
due_time.QuadPart = -sleepTargetDuration.count() * 10;
888+
if (SetWaitableTimer(winPrecisionTimer->handle(), &due_time, 0,
889+
NULL, NULL, FALSE) != TRUE)
890+
{
891+
gzerr << "Could not SetWaitableTimer" << std::endl;
892+
}
893+
else
894+
{
895+
WaitForSingleObject(winPrecisionTimer->handle(), INFINITE);
896+
}
897+
}
898+
else
899+
{
900+
std::this_thread::sleep_until(sleepTarget);
901+
}
902+
#endif
780903
}
781904

782905
// ...then busy-wait for the final moments for precision.
@@ -787,7 +910,7 @@ bool SimulationRunner::Run(const uint64_t _iterations)
787910
}
788911

789912
// Schedule the next update time.
790-
auto now = std::chrono::steady_clock::now();
913+
now = std::chrono::steady_clock::now();
791914
nextUpdateTime += this->updatePeriod;
792915
if (nextUpdateTime < now)
793916
{

src/SimulationRunner.hh

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ namespace ignition
6666
inline namespace IGNITION_GAZEBO_VERSION_NAMESPACE {
6767
// Forward declarations.
6868
class SimulationRunnerPrivate;
69+
#ifdef _WIN32
70+
class SimulationRunnerWinHandleStorage;
71+
#endif
6972

7073
class IGNITION_GAZEBO_VISIBLE SimulationRunner
7174
{
@@ -524,6 +527,29 @@ namespace ignition
524527
/// \brief Set if we need to remove systems due to entity removal
525528
private: bool threadsNeedCleanUp{false};
526529

530+
<<<<<<< HEAD
531+
=======
532+
/// \brief During a forced pause, the user may request that simulation
533+
/// should run. This flag will capture that request, and then be used
534+
/// when the forced pause ends.
535+
private: bool requestedPause{true};
536+
537+
private: bool resetInitiated{false};
538+
539+
/// \brief True to create entities.
540+
private: bool createEntities{false};
541+
private: bool entitiesCreated{false};
542+
543+
/// \brief Flag indicating if the server encountered errors during
544+
/// initialization and should exit immediately. See
545+
/// `SetExitedWithErrors()`.
546+
private: bool exitedWithErrors{false};
547+
#ifdef _WIN32
548+
private: std::unique_ptr<SimulationRunnerWinHandleStorage>
549+
winPrecisionTimer;
550+
#endif
551+
552+
>>>>>>> 503bc6b0 (Use high resolution timer on Windows (#3478))
527553
friend class LevelManager;
528554
};
529555
}

0 commit comments

Comments
 (0)