From 76bf720adf25701dd89ca17115348b744119c3df Mon Sep 17 00:00:00 2001 From: Eladash Date: Sat, 5 Jun 2021 22:15:15 +0300 Subject: [PATCH] Improve emulation stopping speed Split phases of signalling threads and joining them. --- Utilities/Thread.h | 21 ++++++++++++--------- rpcs3/Emu/CPU/CPUThread.cpp | 31 ++++++------------------------- rpcs3/Emu/CPU/CPUThread.h | 6 +++--- rpcs3/Emu/IdManager.h | 27 ++++++++++++++++++++++++++- rpcs3/Emu/System.cpp | 29 +++++++++++++++++++++++++++++ rpcs3/util/fixed_typemap.hpp | 34 +++++++++++++++++++++++++++++++++- 6 files changed, 109 insertions(+), 39 deletions(-) diff --git a/Utilities/Thread.h b/Utilities/Thread.h index 04e5714746..845b29feb6 100644 --- a/Utilities/Thread.h +++ b/Utilities/Thread.h @@ -612,20 +612,24 @@ public: return static_cast(thread::m_sync.load() & 3); } - // Try to abort by assigning thread_state::aborting (UB if assigning different state) + // Try to abort by assigning thread_state::aborting/finished + // Join thread by thread_state::finished named_thread& operator=(thread_state s) { - if (s == thread_state::aborting && thread::m_sync.fetch_op([](u64& v){ return !(v & 3) && (v |= 1); }).second) + if (s >= thread_state::aborting && thread::m_sync.fetch_op([](u64& v){ return !(v & 3) && (v |= 1); }).second) { - if (s == thread_state::aborting) - { - thread::m_sync.notify_one(1); - } + thread::m_sync.notify_one(1); if constexpr (std::is_base_of_v) { this->wake_up(); } + + if (s == thread_state::finished) + { + // This participates in emulation stopping, use destruction-alike semantics + thread::join(true); + } } return *this; @@ -634,9 +638,8 @@ public: // Context type doesn't need virtual destructor ~named_thread() { - // Assign aborting state forcefully - operator=(thread_state::aborting); - thread::join(true); + // Assign aborting state forcefully and join thread + operator=(thread_state::finished); if constexpr (!result::empty) { diff --git a/rpcs3/Emu/CPU/CPUThread.cpp b/rpcs3/Emu/CPU/CPUThread.cpp index 2a4e8376bf..96421925e4 100644 --- a/rpcs3/Emu/CPU/CPUThread.cpp +++ b/rpcs3/Emu/CPU/CPUThread.cpp @@ -827,26 +827,6 @@ void cpu_thread::notify() } } -void cpu_thread::abort() -{ - state += cpu_flag::exit; - state.notify_one(cpu_flag::exit); - - // Downcast to correct type - if (id_type() == 1) - { - *static_cast*>(this) = thread_state::aborting; - } - else if (id_type() == 2) - { - *static_cast*>(this) = thread_state::aborting; - } - else - { - fmt::throw_exception("Invalid cpu_thread type"); - } -} - std::string cpu_thread::get_name() const { // Downcast to correct type @@ -1131,7 +1111,8 @@ void cpu_thread::stop_all() noexcept { auto on_stop = [](u32, cpu_thread& cpu) { - cpu.abort(); + cpu.state += cpu_flag::exit; + cpu.state.notify_one(cpu_flag::exit); }; idm::select>(on_stop); @@ -1139,11 +1120,11 @@ void cpu_thread::stop_all() noexcept } sys_log.notice("All CPU threads have been signaled."); +} - while (s_cpu_counter) - { - std::this_thread::sleep_for(1ms); - } +void cpu_thread::cleanup() noexcept +{ + ensure(!s_cpu_counter); sys_log.notice("All CPU threads have been stopped. [+: %u]", +g_threads_created); diff --git a/rpcs3/Emu/CPU/CPUThread.h b/rpcs3/Emu/CPU/CPUThread.h index 98ae1036d1..5b58a40492 100644 --- a/rpcs3/Emu/CPU/CPUThread.h +++ b/rpcs3/Emu/CPU/CPUThread.h @@ -140,9 +140,6 @@ public: void notify(); -private: - void abort(); - public: // Thread stats for external observation static atomic_t g_threads_created, g_threads_deleted, g_suspend_counter; @@ -269,6 +266,9 @@ public: // Stop all threads with cpu_flag::exit static void stop_all() noexcept; + // Cleanup thread counting information + static void cleanup() noexcept; + // Send signal to the profiler(s) to flush results static void flush_profilers() noexcept; diff --git a/rpcs3/Emu/IdManager.h b/rpcs3/Emu/IdManager.h index 69f7a576f4..1a49015ad2 100644 --- a/rpcs3/Emu/IdManager.h +++ b/rpcs3/Emu/IdManager.h @@ -12,6 +12,8 @@ extern stx::manual_typemap g_fixed_typemap; constexpr auto* g_fxo = &g_fixed_typemap; +enum class thread_state : u32; + // Helper namespace namespace id_manager { @@ -141,7 +143,7 @@ namespace id_manager template struct id_map { - std::vector>> vec{}; + std::vector>> vec{}, private_copy{}; shared_mutex mutex{}; // TODO: Use this instead of global mutex id_map() @@ -149,6 +151,29 @@ namespace id_manager // Preallocate memory vec.reserve(T::id_count); } + + template requires (std::is_assignable_v) + id_map& operator=(thread_state state) + { + if (private_copy.empty()) + { + reader_lock lock(g_mutex); + + // Save all entries + private_copy = vec; + } + + // Signal or join threads + for (const auto& [key, ptr] : private_copy) + { + if (ptr) + { + *static_cast(ptr.get()) = state; + } + } + + return *this; + } }; } diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index b9b74328aa..d9f01de9a0 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -1543,6 +1543,35 @@ void Emulator::Stop(bool restart) } cpu_thread::stop_all(); + + using fxo_t = std::remove_pointer_t; + + // Signal threads + for (const auto& type : fxo_t::view_typelist()) + { + if (type.stop) + { + if (auto data = g_fxo->try_get(type)) + { + type.stop(data, thread_state::aborting); + } + } + } + + // Join threads + for (const auto& type : fxo_t::view_typelist()) + { + if (type.stop) + { + if (auto data = g_fxo->try_get(type)) + { + type.stop(data, thread_state::finished); + } + } + } + + cpu_thread::cleanup(); + g_fxo->reset(); sys_log.notice("All threads have been stopped."); diff --git a/rpcs3/util/fixed_typemap.hpp b/rpcs3/util/fixed_typemap.hpp index 09618ed4f0..64d2f8787b 100644 --- a/rpcs3/util/fixed_typemap.hpp +++ b/rpcs3/util/fixed_typemap.hpp @@ -8,6 +8,8 @@ #include #include +enum class thread_state : u32; + namespace stx { // Simplified typemap with exactly one object of each used type, non-moveable. Initialized on init(). Destroyed on clear(). @@ -53,10 +55,11 @@ namespace stx #endif } - // Save default constructor and destructor + // Save default constructor and destructor and optional joining operation struct typeinfo { bool(*create)(uchar* ptr, manual_typemap&) noexcept = nullptr; + void(*stop)(void* ptr, thread_state) noexcept = nullptr; void(*destroy)(void* ptr) noexcept = nullptr; std::string_view name{}; @@ -86,6 +89,13 @@ namespace stx std::launder(static_cast(ptr))->~T(); } + template + static void call_stop(void* ptr, thread_state state) noexcept + { + // Abort and/or join (expected thread_state::aborting or thread_state::finished) + *std::launder(static_cast(ptr)) = state; + } + template static typeinfo make_typeinfo() { @@ -94,6 +104,12 @@ namespace stx typeinfo r; r.create = &call_ctor; r.destroy = &call_dtor; + + if constexpr (std::is_assignable_v) + { + r.stop = &call_stop; + } + #ifdef _MSC_VER constexpr std::string_view name = parse_type(__FUNCSIG__); #else @@ -327,5 +343,21 @@ namespace stx [[unlikely]] return nullptr; } + + static const auto& view_typelist() noexcept + { + return stx::typelist(); + } + + // Get type-erased raw pointer to storage of type + uchar* try_get(const type_info& type) const noexcept + { + if (m_init[type.index()]) + { + [[likely]] return (Size ? +m_data : m_list) + type.pos(); + } + + [[unlikely]] return nullptr; + } }; }