aboutsummaryrefslogtreecommitdiff
path: root/src/core/arm
diff options
context:
space:
mode:
authorTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-29 05:46:59 -0400
committerTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-29 20:49:47 -0400
commit77cad674e5a73cb56ba1ed5d5c97a0d99eb21159 (patch)
treec8d27ea10578cc82e718a29049d1f05740c871d9 /src/core/arm
parent9c0f7e8ad45200d09052a368a42d4e21a8d99ec4 (diff)
core: pause in guest loops that poll the system counter
Title 1's main thread reads CNTPCT ~17 million times a second while it waits for the GPU (~23% of its samples are the counter read). On an SMT core that spin takes resources from the sibling thread, often the GPU thread. Two reads within ~1.7 us of each other now execute two PAUSE instructions first; the guest sees the same values. EDEN_NX2_CNTPCT_PAUSE=0 turns it off.
Diffstat (limited to 'src/core/arm')
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_64.cpp25
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_64.h1
2 files changed, 25 insertions, 1 deletions
diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.cpp b/src/core/arm/dynarmic/arm_dynarmic_64.cpp
index c2bb05afba..a2c0124f33 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_64.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic_64.cpp
@@ -5,6 +5,10 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <atomic>
+#include <cstdlib>
+#if defined(_M_X64) || defined(__x86_64__)
+#include <immintrin.h>
+#endif
#include "common/settings.h"
#include "core/arm/dynarmic/arm_dynarmic.h"
#include "core/arm/dynarmic/arm_dynarmic_64.h"
@@ -166,7 +170,26 @@ u64 DynarmicCallbacks64::GetTicksRemaining() {
}
u64 DynarmicCallbacks64::GetCNTPCT() {
- return m_parent.m_system.CoreTiming().GetClockTicks();
+ const u64 ticks = m_parent.m_system.CoreTiming().GetClockTicks();
+ // Guest code that waits by polling the counter (title 1's main thread reads it ~17 million
+ // times a second while waiting for the GPU) keeps a host core busy at full speed, and the
+ // GPU thread is often on the other half of that SMT core. Two reads within ~1.7 us of each
+ // other are such a loop: pause, which hands the core's resources to the sibling thread for a
+ // moment. The guest sees the same counter. EDEN_NX2_CNTPCT_PAUSE=0 turns it off.
+ static const bool pause_spins = [] {
+ const char* value = std::getenv("EDEN_NX2_CNTPCT_PAUSE");
+ return value == nullptr || value[0] != '0';
+ }();
+ if (pause_spins && ticks - m_last_cntpct < 32) {
+#if defined(_M_X64) || defined(__x86_64__)
+ _mm_pause();
+ _mm_pause();
+#elif defined(__aarch64__)
+ asm volatile("yield");
+#endif
+ }
+ m_last_cntpct = ticks;
+ return ticks;
}
bool DynarmicCallbacks64::CheckMemoryAccess(u64 addr, u64 size, Kernel::DebugWatchpointType type) {
diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.h b/src/core/arm/dynarmic/arm_dynarmic_64.h
index 5ea5dde8a6..acd1fe773f 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_64.h
+++ b/src/core/arm/dynarmic/arm_dynarmic_64.h
@@ -65,6 +65,7 @@ public:
Core::Memory::Memory& m_memory;
u64 m_tpidrro_el0{};
u64 m_tpidr_el0{};
+ u64 m_last_cntpct{}; ///< The previous counter read (GetCNTPCT spin detection)
Kernel::KProcess* m_process{};
const bool m_debugger_enabled{};
const bool m_check_memory_access{};