From 71b9d8d3df930f783a854b517fa3e982f2b4c30c Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Tue, 28 Jul 2026 23:42:46 -0400 Subject: [PATCH] [TEST] Rework Android CPU affinity/ threading --- .../settings/model/view/SettingsItem.kt | 2 +- src/common/thread.cpp | 119 ++++++++++++++---- src/common/thread.h | 2 +- src/core/cpu_manager.cpp | 7 +- src/video_core/gpu_thread.cpp | 1 + .../renderer_vulkan/vk_pipeline_cache.cpp | 2 +- 6 files changed, 103 insertions(+), 30 deletions(-) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt index 7c6063988f..63ac7b0630 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt @@ -594,7 +594,7 @@ abstract class SettingsItem( IntSetting.ANDROID_PIPELINE_WORKERS, titleId = R.string.pipeline_worker_cores, descriptionId = R.string.pipeline_worker_cores_description, - min = 4, + min = 2, max = 8, units = "cores" ) diff --git a/src/common/thread.cpp b/src/common/thread.cpp index c947c88373..6298e34f8e 100644 --- a/src/common/thread.cpp +++ b/src/common/thread.cpp @@ -39,6 +39,81 @@ #include #endif +#ifdef __ANDROID__ +#include +#include +#include +#include +#include + +namespace { +[[maybe_unused]] constexpr int ANDROID_THREAD_PRIORITY_URGENT_AUDIO = -19; +[[maybe_unused]] constexpr int ANDROID_THREAD_PRIORITY_AUDIO = -16; +[[maybe_unused]] constexpr int ANDROID_THREAD_PRIORITY_URGENT_DISPLAY = -8; +[[maybe_unused]] constexpr int ANDROID_THREAD_PRIORITY_DISPLAY = -4; +[[maybe_unused]] constexpr int ANDROID_THREAD_PRIORITY_FOREGROUND = -2; +[[maybe_unused]] constexpr int ANDROID_THREAD_PRIORITY_MORE_FAVORABLE = -1; +[[maybe_unused]] constexpr int ANDROID_THREAD_PRIORITY_DEFAULT = 0; +[[maybe_unused]] constexpr int ANDROID_THREAD_PRIORITY_LESS_FAVORABLE = 1; +[[maybe_unused]] constexpr int ANDROID_THREAD_PRIORITY_BACKGROUND = 10; +[[maybe_unused]] constexpr int ANDROID_THREAD_PRIORITY_LOWEST = 19; + +constexpr size_t ANDROID_MINIMUM_PERFORMANCE_CORES = 4; + +cpu_set_t ComputePerformanceCoreMask() { + cpu_set_t mask; + CPU_ZERO(&mask); + + cpu_set_t allowed; + CPU_ZERO(&allowed); + if (sched_getaffinity(gettid(), sizeof(allowed), &allowed) != 0) { + return mask; + } + + std::vector> cores; + const int total = static_cast(std::thread::hardware_concurrency()); + for (int cpu = 0; cpu < total; ++cpu) { + if (!CPU_ISSET(cpu, &allowed)) { + continue; + } + long max_frequency = 0; + std::ifstream file("/sys/devices/system/cpu/cpu" + std::to_string(cpu) + + "/cpufreq/cpuinfo_max_freq"); + if (!file || !(file >> max_frequency) || max_frequency <= 0) { + CPU_ZERO(&mask); + return mask; + } + cores.emplace_back(max_frequency, cpu); + } + if (cores.empty()) { + return mask; + } + + std::sort(cores.begin(), cores.end(), + [](const auto& lhs, const auto& rhs) { return lhs.first > rhs.first; }); + + size_t taken = 0; + long cluster_frequency = cores.front().first; + for (const auto& [frequency, cpu] : cores) { + if (frequency != cluster_frequency) { + if (taken >= ANDROID_MINIMUM_PERFORMANCE_CORES) { + break; + } + cluster_frequency = frequency; + } + CPU_SET(cpu, &mask); + ++taken; + } + return mask; +} + +const cpu_set_t& PerformanceCoreMask() { + static const cpu_set_t mask = ComputePerformanceCoreMask(); + return mask; +} +} // Anonymous namespace +#endif + #include "common/cpu_features.h" #ifdef ARCHITECTURE_x86_64 #ifdef _MSC_VER @@ -78,6 +153,21 @@ void SetCurrentThreadPriority(ThreadPriority new_priority) { } }(); set_thread_priority(find_thread(NULL), priority); +#elif defined(__ANDROID__) + const int nice_value = [&]() { + switch (new_priority) { + case ThreadPriority::Low: return ANDROID_THREAD_PRIORITY_BACKGROUND; + case ThreadPriority::Normal: return ANDROID_THREAD_PRIORITY_DEFAULT; + case ThreadPriority::High: return ANDROID_THREAD_PRIORITY_DISPLAY; + case ThreadPriority::VeryHigh: return ANDROID_THREAD_PRIORITY_URGENT_DISPLAY; + case ThreadPriority::Critical: return ANDROID_THREAD_PRIORITY_AUDIO; + default: return ANDROID_THREAD_PRIORITY_DEFAULT; + } + }(); + if (setpriority(PRIO_PROCESS, static_cast(gettid()), nice_value) != 0) { + LOG_DEBUG(Common, "Could not set thread nice value to {}: {}", nice_value, + GetLastErrorMsg()); + } #else pthread_t this_thread = pthread_self(); const auto scheduling_type = SCHED_OTHER; @@ -132,29 +222,16 @@ void SetCurrentThreadName(const char* name) { #endif } -void PinCurrentThreadToPerformanceCore(size_t core_id) { - ASSERT(core_id < 4); - // If we set a flag for a CPU that doesn't exist, the thread may not be allowed to - // run in ANY processor! - auto const total_cores = std::thread::hardware_concurrency(); - if (core_id < total_cores) { +void SetCurrentThreadToPerformanceCores() { #if defined(__ANDROID__) - cpu_set_t set; - CPU_ZERO(&set); - CPU_SET(core_id, &set); - sched_setaffinity(pthread_self(), sizeof(set), &set); -#elif defined(__linux__) || defined(__FreeBSD__) - cpu_set_t set; - CPU_ZERO(&set); - CPU_SET(core_id, &set); - pthread_setaffinity_np(pthread_self(), sizeof(set), &set); -#elif defined(_WIN32) - DWORD set = 1UL << core_id; - SetThreadAffinityMask(GetCurrentThread(), set); -#else - // No pin functionality implemented -#endif + const cpu_set_t& mask = PerformanceCoreMask(); + if (CPU_COUNT(&mask) == 0) { + return; + } + if (sched_setaffinity(gettid(), sizeof(mask), &mask) != 0) { + LOG_DEBUG(Common, "Could not restrict thread to performance cores: {}", GetLastErrorMsg()); } +#endif } #ifdef ARCHITECTURE_x86_64 diff --git a/src/common/thread.h b/src/common/thread.h index a75e342802..80088fbb20 100644 --- a/src/common/thread.h +++ b/src/common/thread.h @@ -101,6 +101,6 @@ enum class ThreadPriority : u32 { void SetCurrentThreadPriority(ThreadPriority new_priority); void SetCurrentThreadName(const char* name); -void PinCurrentThreadToPerformanceCore(size_t core_id); +void SetCurrentThreadToPerformanceCores(); } // namespace Common diff --git a/src/core/cpu_manager.cpp b/src/core/cpu_manager.cpp index 7194fa5022..23d5ac3dfd 100644 --- a/src/core/cpu_manager.cpp +++ b/src/core/cpu_manager.cpp @@ -174,12 +174,7 @@ void CpuManager::RunThread(std::stop_token token, std::size_t core) { std::string name = is_multicore ? ("CPUCore_" + std::to_string(core)) : std::string{"CPUThread"}; Common::SetCurrentThreadName(name.c_str()); Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical); -#ifdef __ANDROID__ - // Aimed specifically for Snapdragon 8 Elite devices - // This kills performance on desktop, but boosts perf for UMA devices - // like the S8E. Mediatek and Mali likely won't suffer. - Common::PinCurrentThreadToPerformanceCore(core); -#endif + Common::SetCurrentThreadToPerformanceCores(); auto& data = core_data[core]; data.host_context = Common::Fiber::ThreadToFiber(); diff --git a/src/video_core/gpu_thread.cpp b/src/video_core/gpu_thread.cpp index 50570b596d..2acf9ac458 100644 --- a/src/video_core/gpu_thread.cpp +++ b/src/video_core/gpu_thread.cpp @@ -30,6 +30,7 @@ void ThreadManager::StartThread(VideoCore::RendererBase& renderer, Core::Fronten thread = std::jthread([&](std::stop_token stop_token) { Common::SetCurrentThreadName("GPU"); Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical); + Common::SetCurrentThreadToPerformanceCores(); system.RegisterHostThread(); auto current_context = context.Acquire(); diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index b1b424ca08..b46b02cd27 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -305,7 +305,7 @@ size_t GetTotalPipelineWorkers() { std::max(static_cast(std::thread::hardware_concurrency()), 2ULL) - 1ULL; #ifdef __ANDROID__ const int configured = AndroidSettings::values.pipeline_worker_count.GetValue(); - const int clamped = std::clamp(configured, 4, 8); + const int clamped = std::clamp(configured, 2, 8); const size_t desired = static_cast(clamped); if (desired == 0) { return 1ULL;