From 543fb282feeb5979ac9b72d718a60fde32fe56d5 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Sun, 31 Oct 2021 19:59:31 +1000 Subject: [PATCH] VMManager: Set affinities for threads --- pcsx2-qt/QtHost.cpp | 2 + pcsx2-qt/Settings/SystemSettingsWidget.cpp | 1 + pcsx2-qt/Settings/SystemSettingsWidget.ui | 46 +++++ pcsx2/CMakeLists.txt | 1 + pcsx2/Config.h | 4 +- pcsx2/GS/Renderers/SW/GSRasterizer.cpp | 24 ++- pcsx2/Pcsx2Config.cpp | 3 + pcsx2/VMManager.cpp | 224 ++++++++++++++++++++- pcsx2/VMManager.h | 7 + pcsx2/pcsx2core.vcxproj | 6 +- 10 files changed, 308 insertions(+), 10 deletions(-) diff --git a/pcsx2-qt/QtHost.cpp b/pcsx2-qt/QtHost.cpp index 1f6a4e0e4d..823c9babd9 100644 --- a/pcsx2-qt/QtHost.cpp +++ b/pcsx2-qt/QtHost.cpp @@ -268,6 +268,8 @@ void QtHost::SetDefaultConfig() { EmuConfig = Pcsx2Config(); EmuFolders::SetDefaults(); + EmuFolders::EnsureFoldersExist(); + VMManager::SetHardwareDependentDefaultSettings(EmuConfig); SettingsInterface& si = *s_base_settings_interface.get(); si.SetUIntValue("UI", "SettingsVersion", SETTINGS_VERSION); diff --git a/pcsx2-qt/Settings/SystemSettingsWidget.cpp b/pcsx2-qt/Settings/SystemSettingsWidget.cpp index bf4a4db941..21ef8f3d96 100644 --- a/pcsx2-qt/Settings/SystemSettingsWidget.cpp +++ b/pcsx2-qt/Settings/SystemSettingsWidget.cpp @@ -40,6 +40,7 @@ SystemSettingsWidget::SystemSettingsWidget(SettingsDialog* dialog, QWidget* pare m_ui.setupUi(this); SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.eeCycleSkipping, "EmuCore/Speedhacks", "EECycleSkip", DEFAULT_EE_CYCLE_SKIP); + SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.affinityControl, "EmuCore/CPU", "AffinityControlMode", 0); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.MTVU, "EmuCore/Speedhacks", "vuThread", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.instantVU1, "EmuCore/Speedhacks", "vu1Instant", true); diff --git a/pcsx2-qt/Settings/SystemSettingsWidget.ui b/pcsx2-qt/Settings/SystemSettingsWidget.ui index ed8fc93b43..d87e2c6840 100644 --- a/pcsx2-qt/Settings/SystemSettingsWidget.ui +++ b/pcsx2-qt/Settings/SystemSettingsWidget.ui @@ -171,6 +171,52 @@ + + + + Affinity Control: + + + + + + + + Disabled + + + + + EE > VU > GS + + + + + EE > GS > VU + + + + + VU > EE > GS + + + + + VU > GS > EE + + + + + GS > EE > VU + + + + + GS > VU > EE + + + + diff --git a/pcsx2/CMakeLists.txt b/pcsx2/CMakeLists.txt index 85277fb186..de68c4a18f 100644 --- a/pcsx2/CMakeLists.txt +++ b/pcsx2/CMakeLists.txt @@ -1604,6 +1604,7 @@ target_link_libraries(PCSX2_FLAGS INTERFACE ryml chdr-static libzip::zip + cpuinfo ZLIB::ZLIB PkgConfig::SOUNDTOUCH PkgConfig::SAMPLERATE diff --git a/pcsx2/Config.h b/pcsx2/Config.h index 7f797ed999..dcf794b0d5 100644 --- a/pcsx2/Config.h +++ b/pcsx2/Config.h @@ -399,6 +399,8 @@ struct Pcsx2Config SSE_MXCSR sseMXCSR; SSE_MXCSR sseVUMXCSR; + u32 AffinityControlMode; + CpuOptions(); void LoadSave(SettingsWrapper& wrap); void ApplySanityCheck(); @@ -407,7 +409,7 @@ struct Pcsx2Config bool operator==(const CpuOptions& right) const { - return OpEqu(sseMXCSR) && OpEqu(sseVUMXCSR) && OpEqu(Recompiler); + return OpEqu(sseMXCSR) && OpEqu(sseVUMXCSR) && OpEqu(AffinityControlMode) && OpEqu(Recompiler); } bool operator!=(const CpuOptions& right) const diff --git a/pcsx2/GS/Renderers/SW/GSRasterizer.cpp b/pcsx2/GS/Renderers/SW/GSRasterizer.cpp index ff51707d2d..bd8eed1bb4 100644 --- a/pcsx2/GS/Renderers/SW/GSRasterizer.cpp +++ b/pcsx2/GS/Renderers/SW/GSRasterizer.cpp @@ -21,6 +21,10 @@ #include "PerformanceMetrics.h" #include "common/StringUtil.h" +#ifdef PCSX2_CORE +#include "VMManager.h" +#endif + #define ENABLE_DRAW_STATS 0 int GSRasterizerData::s_counter = 0; @@ -1176,7 +1180,25 @@ GSRasterizerList::~GSRasterizerList() void GSRasterizerList::OnWorkerStartup(int i) { Threading::SetNameOfCurrentThread(StringUtil::StdStringFromFormat("GS-SW-%d", i).c_str()); - PerformanceMetrics::SetGSSWThread(i, Threading::ThreadHandle::GetForCallingThread()); + + Threading::ThreadHandle handle(Threading::ThreadHandle::GetForCallingThread()); + +#ifdef PCSX2_CORE + if (EmuConfig.Cpu.AffinityControlMode != 0) + { + const std::vector& procs = VMManager::GetSortedProcessorList(); + const u32 processor_index = (THREAD_VU1 ? 3 : 2) + i; + if (processor_index < procs.size()) + { + const u32 procid = procs[processor_index]; + const u64 affinity = static_cast(1) << procid; + Console.WriteLn("Pinning GS thread %d to CPU %u (0x%llx)", i, procid, affinity); + handle.SetAffinity(affinity); + } + } +#endif + + PerformanceMetrics::SetGSSWThread(i, std::move(handle)); } void GSRasterizerList::OnWorkerShutdown(int i) diff --git a/pcsx2/Pcsx2Config.cpp b/pcsx2/Pcsx2Config.cpp index 25ae84f2bf..b74796f37b 100644 --- a/pcsx2/Pcsx2Config.cpp +++ b/pcsx2/Pcsx2Config.cpp @@ -238,12 +238,14 @@ Pcsx2Config::CpuOptions::CpuOptions() { sseMXCSR.bitmask = DEFAULT_sseMXCSR; sseVUMXCSR.bitmask = DEFAULT_sseVUMXCSR; + AffinityControlMode = 0; } void Pcsx2Config::CpuOptions::ApplySanityCheck() { sseMXCSR.ClearExceptionFlags().DisableExceptions(); sseVUMXCSR.ClearExceptionFlags().DisableExceptions(); + AffinityControlMode = std::min(AffinityControlMode, 6); Recompiler.ApplySanityCheck(); } @@ -255,6 +257,7 @@ void Pcsx2Config::CpuOptions::LoadSave(SettingsWrapper& wrap) SettingsWrapBitBoolEx(sseMXCSR.DenormalsAreZero, "FPU.DenormalsAreZero"); SettingsWrapBitBoolEx(sseMXCSR.FlushToZero, "FPU.FlushToZero"); SettingsWrapBitfieldEx(sseMXCSR.RoundingControl, "FPU.Roundmode"); + SettingsWrapEntry(AffinityControlMode); SettingsWrapBitBoolEx(sseVUMXCSR.DenormalsAreZero, "VU.DenormalsAreZero"); SettingsWrapBitBoolEx(sseVUMXCSR.FlushToZero, "VU.FlushToZero"); diff --git a/pcsx2/VMManager.cpp b/pcsx2/VMManager.cpp index cc4b14c315..07f41a3a98 100644 --- a/pcsx2/VMManager.cpp +++ b/pcsx2/VMManager.cpp @@ -18,6 +18,7 @@ #include "VMManager.h" #include +#include #include #include "common/Console.h" @@ -26,6 +27,7 @@ #include "common/StringUtil.h" #include "common/SettingsWrapper.h" #include "common/Timer.h" +#include "common/Threading.h" #include "fmt/core.h" #include "Counters.h" @@ -98,7 +100,8 @@ namespace VMManager std::string filename, s32 slot_for_message); static void SetTimerResolutionIncreased(bool enabled); - static void SetEmuThreadAffinities(bool force); + static void EnsureCPUInfoInitialized(); + static void SetEmuThreadAffinities(); } // namespace VMManager static std::unique_ptr s_vm_memory; @@ -924,7 +927,7 @@ bool VMManager::Initialize(const VMBootParameters& boot_params) UpdateRunningGame(true, false); - SetEmuThreadAffinities(true); + SetEmuThreadAffinities(); PerformanceMetrics::Clear(); @@ -1410,6 +1413,12 @@ void VMManager::CheckForCPUConfigChanges(const Pcsx2Config& old_config) // possible and reset next time we're called. s_cpu_implementation_changed = true; } + + if (EmuConfig.Cpu.AffinityControlMode != old_config.Cpu.AffinityControlMode || + EmuConfig.Speedhacks.vuThread != old_config.Speedhacks.vuThread) + { + SetEmuThreadAffinities(); + } } void VMManager::CheckForGSConfigChanges(const Pcsx2Config& old_config) @@ -1573,10 +1582,7 @@ void VMManager::ApplySettings() LoadSettings(); if (HasValidVM()) - { CheckForConfigChanges(old_config); - SetEmuThreadAffinities(false); - } } bool VMManager::ReloadGameSettings() @@ -1722,7 +1728,211 @@ void VMManager::SetTimerResolutionIncreased(bool enabled) #endif -void VMManager::SetEmuThreadAffinities(bool force) +static std::vector s_processor_list; +static std::once_flag s_processor_list_initialized; + +#if defined(__linux__) || defined(_WIN32) + +#include "cpuinfo.h" + +static u32 GetProcessorIdForProcessor(const cpuinfo_processor* proc) { - Console.Error("(SetEmuThreadAffinities) Not implemented"); +#if defined(__linux__) + return static_cast(proc->linux_id); +#elif defined(_WIN32) + return static_cast(proc->windows_processor_id); +#else + return 0; +#endif +} + +static void InitializeCPUInfo() +{ + if (!cpuinfo_initialize()) + { + Console.Error("Failed to initialize cpuinfo"); + return; + } + + const u32 cluster_count = cpuinfo_get_clusters_count(); + if (cluster_count == 0) + { + Console.Error("Invalid CPU count returned"); + return; + } + + Console.WriteLn(Color_StrongYellow, "Processor count: %u cores, %u processors", cpuinfo_get_cores_count(), cpuinfo_get_processors_count()); + Console.WriteLn(Color_StrongYellow, "Cluster count: %u", cluster_count); + + static std::vector ordered_processors; + for (u32 i = 0; i < cluster_count; i++) + { + const cpuinfo_cluster* cluster = cpuinfo_get_cluster(i); + for (u32 j = 0; j < cluster->processor_count; j++) + { + const cpuinfo_processor* proc = cpuinfo_get_processor(cluster->processor_start + j); + if (!proc) + continue; + + ordered_processors.push_back(proc); + } + } + // find the large and small clusters based on frequency + // this is assuming the large cluster is always clocked higher + // sort based on core, so that hyperthreads get pushed down + std::sort(ordered_processors.begin(), ordered_processors.end(), [](const cpuinfo_processor* lhs, const cpuinfo_processor* rhs) { + return (lhs->core->frequency > rhs->core->frequency || lhs->smt_id < rhs->smt_id); + }); + + s_processor_list.reserve(ordered_processors.size()); + std::stringstream ss; + ss << "Ordered processor list: "; + for (const cpuinfo_processor* proc : ordered_processors) + { + if (proc != ordered_processors.front()) + ss << ", "; + + const u32 procid = GetProcessorIdForProcessor(proc); + ss << procid; + if (proc->smt_id != 0) + ss << "[SMT " << proc->smt_id << "]"; + + s_processor_list.push_back(procid); + } + Console.WriteLn(ss.str()); +} + +static void SetMTVUAndAffinityControlDefault(Pcsx2Config& config) +{ + VMManager::EnsureCPUInfoInitialized(); + + const u32 cluster_count = cpuinfo_get_clusters_count(); + if (cluster_count == 0) + { + Console.Error("Invalid CPU count returned"); + return; + } + + Console.WriteLn("Cluster count: %u", cluster_count); + + for (u32 i = 0; i < cluster_count; i++) + { + const cpuinfo_cluster* cluster = cpuinfo_get_cluster(i); + Console.WriteLn(" Cluster %u: %u cores and %u processors at %u MHz", + i, cluster->core_count, cluster->processor_count, static_cast(cluster->frequency /* / 1000000u*/)); + } + + const bool has_big_little = cluster_count > 1; + Console.WriteLn("Big-Little: %s", has_big_little ? "yes" : "no"); + + const u32 big_cores = cpuinfo_get_cluster(0)->core_count + ((cluster_count > 2) ? cpuinfo_get_cluster(1)->core_count : 0u); + Console.WriteLn("Guessing we have %u big/medium cores...", big_cores); + + bool mtvu_enable; + bool affinity_control; + if (big_cores >= 3 || big_cores == 1) + { + Console.WriteLn(" So enabling MTVU and disabling affinity control"); + mtvu_enable = true; + affinity_control = false; + } + else + { + Console.WriteLn(" So disabling MTVU and enabling affinity control"); + mtvu_enable = false; + affinity_control = true; + } + + config.Speedhacks.vuThread = mtvu_enable; + config.Cpu.AffinityControlMode = affinity_control ? 1 : 0; +} + +#else + +static void InitializeCPUInfo() +{ + DevCon.WriteLn("(VMManager) InitializeCPUInfo() not implemented."); +} + +static void SetMTVUAndAffinityControlDefault(Pcsx2Config& config) +{ +} + +#endif + +void VMManager::EnsureCPUInfoInitialized() +{ + std::call_once(s_processor_list_initialized, InitializeCPUInfo); +} + +void VMManager::SetEmuThreadAffinities() +{ + EnsureCPUInfoInitialized(); + + if (s_processor_list.empty()) + { + // not supported on this platform + return; + } + + if (EmuConfig.Cpu.AffinityControlMode == 0 || + s_processor_list.size() < (EmuConfig.Speedhacks.vuThread ? 3 : 2)) + { + if (EmuConfig.Cpu.AffinityControlMode != 0) + Console.Error("Insufficient processors for affinity control."); + + GetMTGS().GetThreadHandle().SetAffinity(0); + vu1Thread.GetThreadHandle().SetAffinity(0); + s_vm_thread_handle.SetAffinity(0); + return; + } + + static constexpr u8 processor_assignment[7][2][3] = { + //EE xx GS EE VU GS + {{0, 2, 1}, {0, 1, 2}}, // Disabled + {{0, 2, 1}, {0, 1, 2}}, // EE > VU > GS + {{0, 2, 1}, {0, 2, 1}}, // EE > GS > VU + {{0, 2, 1}, {1, 0, 2}}, // VU > EE > GS + {{1, 2, 0}, {2, 0, 1}}, // VU > GS > EE + {{1, 2, 0}, {1, 2, 0}}, // GS > EE > VU + {{1, 2, 0}, {2, 1, 0}}, // GS > VU > EE + }; + + // steal vu's thread if mtvu is off + const u8* this_proc_assigment = processor_assignment[EmuConfig.Cpu.AffinityControlMode][EmuConfig.Speedhacks.vuThread]; + const u32 ee_index = s_processor_list[this_proc_assigment[0]]; + const u32 vu_index = s_processor_list[this_proc_assigment[1]]; + const u32 gs_index = s_processor_list[this_proc_assigment[2]]; + Console.WriteLn("Processor order assignment: EE=%u, VU=%u, GS=%u", + this_proc_assigment[0], this_proc_assigment[1], this_proc_assigment[2]); + + const u64 ee_affinity = static_cast(1) << ee_index; + Console.WriteLn(Color_StrongGreen, "EE thread is on processor %u (0x%llx)", ee_index, ee_affinity); + s_vm_thread_handle.SetAffinity(ee_affinity); + + if (EmuConfig.Speedhacks.vuThread) + { + const u64 vu_affinity = static_cast(1) << vu_index; + Console.WriteLn(Color_StrongGreen, "VU thread is on processor %u (0x%llx)", vu_index, vu_affinity); + vu1Thread.GetThreadHandle().SetAffinity(vu_affinity); + } + else + { + vu1Thread.GetThreadHandle().SetAffinity(0); + } + + const u64 gs_affinity = static_cast(1) << gs_index; + Console.WriteLn(Color_StrongGreen, "GS thread is on processor %u (0x%llx)", gs_index, gs_affinity); + GetMTGS().GetThreadHandle().SetAffinity(gs_affinity); +} + +void VMManager::SetHardwareDependentDefaultSettings(Pcsx2Config& config) +{ + SetMTVUAndAffinityControlDefault(config); +} + +const std::vector& VMManager::GetSortedProcessorList() +{ + EnsureCPUInfoInitialized(); + return s_processor_list; } diff --git a/pcsx2/VMManager.h b/pcsx2/VMManager.h index a7814213b2..61a935e76f 100644 --- a/pcsx2/VMManager.h +++ b/pcsx2/VMManager.h @@ -159,6 +159,13 @@ namespace VMManager /// If the scale is set to 0, the internal resolution will be used, otherwise it is treated as a multiplier to 1x. void RequestDisplaySize(float scale = 0.0f); + /// Sets default settings based on hardware configuration. + void SetHardwareDependentDefaultSettings(Pcsx2Config& config); + + /// Returns a list of processors in the system, and their corresponding affinity mask. + /// This list is ordered by most performant to least performant for pinning threads to. + const std::vector& GetSortedProcessorList(); + /// Internal callbacks, implemented in the emu core. namespace Internal { diff --git a/pcsx2/pcsx2core.vcxproj b/pcsx2/pcsx2core.vcxproj index d27074ff32..1a7076ea14 100644 --- a/pcsx2/pcsx2core.vcxproj +++ b/pcsx2/pcsx2core.vcxproj @@ -48,6 +48,7 @@ $(SolutionDir)3rdparty\libzip;$(SolutionDir)3rdparty\libzip\libzip\lib;%(AdditionalIncludeDirectories) $(SolutionDir)3rdparty\d3d12memalloc\include;%(AdditionalIncludeDirectories) %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\zstd\zstd\lib + %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\cpuinfo\include Async Use PrecompiledHeader.h @@ -791,6 +792,9 @@ {20b2e9fe-f020-42a0-b324-956f5b06ea68} + + {7e183337-a7e9-460c-9d3d-568bc9f9bcc1} + {812b4434-fd6b-4cb2-8865-5fd8eb34b046} @@ -803,4 +807,4 @@ - \ No newline at end of file +