1
0
mirror of https://github.com/RPCS3/rpcs3.git synced 2024-11-22 10:42:36 +01:00

Improve emulation stopping speed

Split phases of signalling threads and joining them.
This commit is contained in:
Eladash 2021-06-05 22:15:15 +03:00 committed by Ivan
parent 2169e8d935
commit 76bf720adf
6 changed files with 109 additions and 39 deletions

View File

@ -612,20 +612,24 @@ public:
return static_cast<thread_state>(thread::m_sync.load() & 3); return static_cast<thread_state>(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) 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<need_wakeup, Context>) if constexpr (std::is_base_of_v<need_wakeup, Context>)
{ {
this->wake_up(); this->wake_up();
} }
if (s == thread_state::finished)
{
// This participates in emulation stopping, use destruction-alike semantics
thread::join(true);
}
} }
return *this; return *this;
@ -634,9 +638,8 @@ public:
// Context type doesn't need virtual destructor // Context type doesn't need virtual destructor
~named_thread() ~named_thread()
{ {
// Assign aborting state forcefully // Assign aborting state forcefully and join thread
operator=(thread_state::aborting); operator=(thread_state::finished);
thread::join(true);
if constexpr (!result::empty) if constexpr (!result::empty)
{ {

View File

@ -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<named_thread<ppu_thread>*>(this) = thread_state::aborting;
}
else if (id_type() == 2)
{
*static_cast<named_thread<spu_thread>*>(this) = thread_state::aborting;
}
else
{
fmt::throw_exception("Invalid cpu_thread type");
}
}
std::string cpu_thread::get_name() const std::string cpu_thread::get_name() const
{ {
// Downcast to correct type // Downcast to correct type
@ -1131,7 +1111,8 @@ void cpu_thread::stop_all() noexcept
{ {
auto on_stop = [](u32, cpu_thread& cpu) auto on_stop = [](u32, cpu_thread& cpu)
{ {
cpu.abort(); cpu.state += cpu_flag::exit;
cpu.state.notify_one(cpu_flag::exit);
}; };
idm::select<named_thread<ppu_thread>>(on_stop); idm::select<named_thread<ppu_thread>>(on_stop);
@ -1139,12 +1120,12 @@ void cpu_thread::stop_all() noexcept
} }
sys_log.notice("All CPU threads have been signaled."); 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); sys_log.notice("All CPU threads have been stopped. [+: %u]", +g_threads_created);
g_threads_deleted -= g_threads_created.load(); g_threads_deleted -= g_threads_created.load();

View File

@ -140,9 +140,6 @@ public:
void notify(); void notify();
private:
void abort();
public: public:
// Thread stats for external observation // Thread stats for external observation
static atomic_t<u64> g_threads_created, g_threads_deleted, g_suspend_counter; static atomic_t<u64> g_threads_created, g_threads_deleted, g_suspend_counter;
@ -269,6 +266,9 @@ public:
// Stop all threads with cpu_flag::exit // Stop all threads with cpu_flag::exit
static void stop_all() noexcept; static void stop_all() noexcept;
// Cleanup thread counting information
static void cleanup() noexcept;
// Send signal to the profiler(s) to flush results // Send signal to the profiler(s) to flush results
static void flush_profilers() noexcept; static void flush_profilers() noexcept;

View File

@ -12,6 +12,8 @@ extern stx::manual_typemap<void, 0x20'00000, 128> g_fixed_typemap;
constexpr auto* g_fxo = &g_fixed_typemap; constexpr auto* g_fxo = &g_fixed_typemap;
enum class thread_state : u32;
// Helper namespace // Helper namespace
namespace id_manager namespace id_manager
{ {
@ -141,7 +143,7 @@ namespace id_manager
template <typename T> template <typename T>
struct id_map struct id_map
{ {
std::vector<std::pair<id_key, std::shared_ptr<void>>> vec{}; std::vector<std::pair<id_key, std::shared_ptr<void>>> vec{}, private_copy{};
shared_mutex mutex{}; // TODO: Use this instead of global mutex shared_mutex mutex{}; // TODO: Use this instead of global mutex
id_map() id_map()
@ -149,6 +151,29 @@ namespace id_manager
// Preallocate memory // Preallocate memory
vec.reserve(T::id_count); vec.reserve(T::id_count);
} }
template <bool dummy = false> requires (std::is_assignable_v<T&, thread_state>)
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<T*>(ptr.get()) = state;
}
}
return *this;
}
}; };
} }

View File

@ -1543,6 +1543,35 @@ void Emulator::Stop(bool restart)
} }
cpu_thread::stop_all(); cpu_thread::stop_all();
using fxo_t = std::remove_pointer_t<decltype(g_fxo)>;
// 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(); g_fxo->reset();
sys_log.notice("All threads have been stopped."); sys_log.notice("All threads have been stopped.");

View File

@ -8,6 +8,8 @@
#include <utility> #include <utility>
#include <type_traits> #include <type_traits>
enum class thread_state : u32;
namespace stx namespace stx
{ {
// Simplified typemap with exactly one object of each used type, non-moveable. Initialized on init(). Destroyed on clear(). // 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 #endif
} }
// Save default constructor and destructor // Save default constructor and destructor and optional joining operation
struct typeinfo struct typeinfo
{ {
bool(*create)(uchar* ptr, manual_typemap&) noexcept = nullptr; bool(*create)(uchar* ptr, manual_typemap&) noexcept = nullptr;
void(*stop)(void* ptr, thread_state) noexcept = nullptr;
void(*destroy)(void* ptr) noexcept = nullptr; void(*destroy)(void* ptr) noexcept = nullptr;
std::string_view name{}; std::string_view name{};
@ -86,6 +89,13 @@ namespace stx
std::launder(static_cast<T*>(ptr))->~T(); std::launder(static_cast<T*>(ptr))->~T();
} }
template <typename T>
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<T*>(ptr)) = state;
}
template <typename T> template <typename T>
static typeinfo make_typeinfo() static typeinfo make_typeinfo()
{ {
@ -94,6 +104,12 @@ namespace stx
typeinfo r; typeinfo r;
r.create = &call_ctor<T>; r.create = &call_ctor<T>;
r.destroy = &call_dtor<T>; r.destroy = &call_dtor<T>;
if constexpr (std::is_assignable_v<T&, thread_state>)
{
r.stop = &call_stop<T>;
}
#ifdef _MSC_VER #ifdef _MSC_VER
constexpr std::string_view name = parse_type(__FUNCSIG__); constexpr std::string_view name = parse_type(__FUNCSIG__);
#else #else
@ -327,5 +343,21 @@ namespace stx
[[unlikely]] return nullptr; [[unlikely]] return nullptr;
} }
static const auto& view_typelist() noexcept
{
return stx::typelist<typeinfo>();
}
// Get type-erased raw pointer to storage of type
uchar* try_get(const type_info<typeinfo>& type) const noexcept
{
if (m_init[type.index()])
{
[[likely]] return (Size ? +m_data : m_list) + type.pos();
}
[[unlikely]] return nullptr;
}
}; };
} }