diff options
Diffstat (limited to 'src/common/thread.cpp')
| -rw-r--r-- | src/common/thread.cpp | 106 |
1 files changed, 104 insertions, 2 deletions
diff --git a/src/common/thread.cpp b/src/common/thread.cpp index f4bdb3f7c0..49f6f5cf98 100644 --- a/src/common/thread.cpp +++ b/src/common/thread.cpp @@ -4,6 +4,8 @@ // SPDX-FileCopyrightText: 2014 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include <chrono> +#include <limits> #include <string> #include <thread> @@ -18,24 +20,35 @@ #elif defined(_WIN32) #include <windows.h> #include "common/string_util.h" +#include "common/windows/timer_resolution.h" #else #if defined(__FreeBSD__) #include <sys/cpuset.h> #include <sys/_cpuset.h> #include <pthread_np.h> +// Compatibility with CPUset +#define cpu_set_t cpuset_t #elif defined(__DragonFly__) || defined(__OpenBSD__) || defined(__Bitrig__) #include <pthread_np.h> #endif #include <pthread.h> #include <sched.h> #endif + #ifndef _WIN32 #include <unistd.h> #endif -#ifdef __FreeBSD__ -# define cpu_set_t cpuset_t +#include "common/cpu_features.h" +#ifdef ARCHITECTURE_x86_64 +#ifdef _MSC_VER +#include <intrin.h> +#else +#include <x86intrin.h> #endif +#include "common/x64/rdtsc.h" +#endif +#include "core/core_timing.h" namespace Common { @@ -144,4 +157,93 @@ void PinCurrentThreadToPerformanceCore(size_t core_id) { } } +#ifdef ARCHITECTURE_x86_64 +// On Linux and UNIX systems, a futex would nominally be used to cover the costs +// the idea is that it's intuitivelly cheaper to use a direct instruction as opposed to a full futex call +// the underlying libc++ implementation uses pthread_cond_timedwait which MAY invoke a futex +// Let's pretend the OS is too expensive to jump into, and avoid ANY context switches +// this should *IN THEORY* lower CPU usage while just waiting for stuff effectively +// For windows the minimal quanta resolution is about 500us, and normal CRT cond var is 1.5ms(?) +// so may as well avoid that too +// Let's just give ALL platforms the same mechanisms (almost) for when they have umonitor OR waitpkg +#ifdef __clang__ +__attribute__((target("waitpkg,mwaitx"))) +#elif defined(__GNUC__) +#pragma GCC target("waitpkg") +#pragma GCC target("mwaitx") +#endif +bool Event::WaitFor(const std::chrono::nanoseconds time) { +#ifdef _WIN32 + auto const start = Common::X64::FencedRDTSC(); + auto const& caps = Common::g_cpu_caps; + [[maybe_unused]] auto const end = start + Common::g_wall_clock.NsToTicks(time); + if (caps.monitorx) { + while (true) { + // Armed monitor, as per manual, MWAITX must be conditional if the condition isn't satisfied + // to prevent a race condition. + _mm_monitorx(reinterpret_cast<u64*>(std::addressof(is_set)), 0, 0); + if (!is_set.load()) { + // RDTSC may be fenced here due to atomic load + auto const now = _rdtsc(); + if (end > now) { + u32 const cycles = std::min<u32>((std::numeric_limits<u32>::max)(), s64(end) - s64(now)); + // See here: https://github.com/torvalds/linux/blob/948a64995aca6820abefd17f1a4258f5835c5ad9/arch/x86/lib/delay.c#L93 + // MWAITX accepts a 32-bit input timer which determines the total number of cycles to wait for + // NOT THE TOTAL ABSOLUTE TSC VALUE, it's just a delta + // BIT[1] = use a timer + // Hint = 0: Use C1 state when sleepy (means slower wakeup but better savings) + _mm_mwaitx(1 << 1, 0u, cycles); + if (!is_set.load()) + return false; + } else + return false; //timeout + } + bool expected = true; + if (is_set.compare_exchange_weak(expected, false, std::memory_order_release)) + return true; + } + } else if (caps.waitpkg) { + // #UD If CPUID.7.0:ECX.WAITPKG[bit 5]=0. + while (true) { + _umonitor(std::addressof(is_set)); + if (!is_set.load() && !_umwait(0, end)) //umwait is absolute time!!! + return false; + bool expected = true; + if (is_set.compare_exchange_weak(expected, false, std::memory_order_release)) + return true; + } + } else { + while (!is_set.load() && end > _rdtsc()) + Common::Windows::SleepForOneTick(); + if (is_set.load()) + Reset(); + return true; + } +#else + std::unique_lock lk{mutex}; + if (!condvar.wait_for(lk, time, [this] { return is_set.load(); })) + return false; + is_set = false; + return true; +#endif +} +#else +bool Event::WaitFor(const std::chrono::nanoseconds time) { +#ifdef _WIN32 + auto const end = Common::g_wall_clock.GetTimeNS() + time; + while (!is_set.load() && end > Common::g_wall_clock.GetTimeNS()) + Common::Windows::SleepForOneTick(); + if (is_set.load()) + Reset(); + return true; +#else + std::unique_lock lk{mutex}; + if (!condvar.wait_for(lk, time, [this] { return is_set.load(); })) + return false; + is_set = false; + return true; +#endif +} +#endif + } // namespace Common |
