From b1e57bc7bcb2092f66e72b193c650f1bd1eb0baf Mon Sep 17 00:00:00 2001 From: Billy Laws Date: Mon, 27 Feb 2023 22:05:24 +0000 Subject: [PATCH] Introduce adapting condition variable class By spin waiting for a small period before falling back to an actual condition variable, some of the overheads inherent to futex's can be avoided. The used constants were tuned for optimal performance on 8G1 on Skyrim and PGLE. --- app/src/main/cpp/skyline/common/spin_lock.cpp | 30 +++- app/src/main/cpp/skyline/common/spin_lock.h | 130 ++++++++++++++++++ 2 files changed, 154 insertions(+), 6 deletions(-) diff --git a/app/src/main/cpp/skyline/common/spin_lock.cpp b/app/src/main/cpp/skyline/common/spin_lock.cpp index 8c0e343e..d0f22bda 100644 --- a/app/src/main/cpp/skyline/common/spin_lock.cpp +++ b/app/src/main/cpp/skyline/common/spin_lock.cpp @@ -4,15 +4,19 @@ #include #include #include "spin_lock.h" +#include "utils.h" namespace skyline { - static constexpr size_t LockAttemptsPerYield{256}; + static constexpr size_t LockAttemptsPerYield{32}; static constexpr size_t LockAttemptsPerSleep{1024}; - static constexpr size_t SleepDurationUs{100}; + static constexpr size_t SleepDurationUs{50}; template void FalloffLock(Func &&func) { - for (size_t i{}; !func(); i++) { + for (size_t i{1}; !func(i); i++) { + asm volatile("DMB ISHST;" + "YIELD;"); + if (i % LockAttemptsPerYield == 0) std::this_thread::yield(); if (i % LockAttemptsPerSleep == 0) @@ -21,20 +25,34 @@ namespace skyline { } void __attribute__ ((noinline)) SpinLock::LockSlow() { - FalloffLock([this] { + FalloffLock([this] (size_t i) { return try_lock(); }); } void __attribute__ ((noinline)) SharedSpinLock::LockSlow() { - FalloffLock([this] { + FalloffLock([this] (size_t i) { return try_lock(); }); } void __attribute__ ((noinline)) SharedSpinLock::LockSlowShared() { - FalloffLock([this] { + FalloffLock([this] (size_t i) { return try_lock_shared(); }); } + + static constexpr size_t AdaptiveWaitIters{1024}; //!< Number of wait iterations before waiting should fallback to a regular condition variable + + void __attribute__ ((noinline)) AdaptiveSingleWaiterConditionVariable::SpinWait() { + FalloffLock([this] (size_t i) { + return !unsignalled.test_and_set() || i >= AdaptiveWaitIters; + }); + } + + void __attribute__ ((noinline)) AdaptiveSingleWaiterConditionVariable::SpinWait(i64 maxEndTimeNs) { + FalloffLock([maxEndTimeNs, this] (size_t i) { + return util::GetTimeNs() > maxEndTimeNs || !unsignalled.test_and_set() || i >= AdaptiveWaitIters; + }); + } } diff --git a/app/src/main/cpp/skyline/common/spin_lock.h b/app/src/main/cpp/skyline/common/spin_lock.h index 70ef9cac..a0f9bb74 100644 --- a/app/src/main/cpp/skyline/common/spin_lock.h +++ b/app/src/main/cpp/skyline/common/spin_lock.h @@ -4,8 +4,11 @@ #pragma once #include +#include #include +#include #include "base.h" +#include "utils.h" namespace skyline { /** @@ -130,4 +133,131 @@ namespace skyline { } } }; + + /** + * @brief A condition variable that spins for a bit before falling back to a regular condition variable, for cases where only a single thread can wait at the same time + */ + class AdaptiveSingleWaiterConditionVariable { + private: + /** + * @brief Spins either until the condition variable is signalled or the spin wait times out (to fall back to a regular condition variable) + */ + void SpinWait(); + + /** + * @brief Spins either until the condition variable is signalled or the spin wait times out (to fall back to a regular condition variable, or until the given time is reached) + * @param maxEndTimeNs The maximum time to spin for + */ + void SpinWait(i64 maxEndTimeNs); + + std::condition_variable fallback; // endTimeNs) + return false; + + + // If the spin wait timed out (due to wanting to fallback), then fallback to a regular condition variable + + // Calculate chrono-based end time only in the fallback path to avoid polluting the fast past + auto endTime{std::chrono::system_clock::now() + std::chrono::nanoseconds(endTimeNs - util::GetTimeNs())}; + std::cv_status status{std::cv_status::no_timeout}; + while (status == std::cv_status::no_timeout && !pred()) { + std::unique_lock fallbackLock{fallbackMutex}; + + // Store that we are currently waiting on the fallback condition variable, `notify()` can check this when `fallbackLock` ends up being unlocked by `fallback.wait()` to avoid redundantly performing futex wakes (on older bionic versions) + fallbackWaiter = true; + + lock.unlock(); + status = fallback.wait_until(fallbackLock, endTime); + + fallbackWaiter = false; + fallbackLock.unlock(); + + lock.lock(); + } + + // The predicate has been satisfied, we're done here + return pred(); + } + }; }