Compare commits

..

2 Commits

Author SHA1 Message Date
Caio Oliveira
112b14b564 [chore] fix build errors
Signed-off-by: Caio Oliveira <caiooliveirafarias0@gmail.com>
2025-12-17 21:46:22 -03:00
lizzie
754883db97 [core] pin core threads to logical CPUs 0-3
this basically allows the threads to exist in these logical CPUs, undisturbed, and without trashing each other's cache
this could improve performance, very tricky thing to pull off correctly, but again, this is mostly an experiment
will mainly benefit: Linux, Android, FreeBSD, Windows (not ARM)
Additionally, this means no context trashing :)

Signed-off-by: lizzie <lizzie@eden-emu.dev>
2025-12-17 21:23:30 -03:00
6 changed files with 119 additions and 94 deletions

View File

@@ -5,9 +5,11 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <string>
#include <thread>
#include "common/error.h"
#include "common/logging/log.h"
#include "common/assert.h"
#include "common/thread.h"
#ifdef __APPLE__
#include <mach/mach.h>
@@ -18,6 +20,8 @@
#include "common/string_util.h"
#else
#if defined(__Bitrig__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__)
#include <sys/cpuset.h>
#include <sys/_cpuset.h>
#include <pthread_np.h>
#endif
#include <pthread.h>
@@ -28,7 +32,7 @@
#endif
#ifdef __FreeBSD__
#define cpu_set_t cpuset_t
# define cpu_set_t cpuset_t
#endif
namespace Common {
@@ -77,22 +81,14 @@ void SetCurrentThreadPriority(ThreadPriority new_priority) {
#endif
}
void SetCurrentThreadName(const char* name) {
#ifdef _MSC_VER
// Sets the debugger-visible name of the current thread.
void SetCurrentThreadName(const char* name) {
static auto pf = (decltype(&SetThreadDescription))(void*)GetProcAddress(GetModuleHandle(TEXT("KernelBase.dll")), "SetThreadDescription");
if (pf)
// Sets the debugger-visible name of the current thread.
if (auto pf = (decltype(&SetThreadDescription))(void*)GetProcAddress(GetModuleHandle(TEXT("KernelBase.dll")), "SetThreadDescription"); pf)
pf(GetCurrentThread(), UTF8ToUTF16W(name).data()); // Windows 10+
}
#else // !MSVC_VER, so must be POSIX threads
// MinGW with the POSIX threading model does not support pthread_setname_np
void SetCurrentThreadName(const char* name) {
// See for reference
// https://gitlab.freedesktop.org/mesa/mesa/-/blame/main/src/util/u_thread.c?ref_type=heads#L75
#ifdef __APPLE__
else
; // No-op
#elif defined(__APPLE__)
pthread_setname_np(name);
#elif defined(__HAIKU__)
rename_thread(find_thread(NULL), name);
@@ -112,13 +108,33 @@ void SetCurrentThreadName(const char* name) {
pthread_setname_np(pthread_self(), buf);
}
#elif defined(_WIN32)
// mingw stub
// MinGW with the POSIX threading model does not support pthread_setname_np
// See for reference
// https://gitlab.freedesktop.org/mesa/mesa/-/blame/main/src/util/u_thread.c?ref_type=heads#L75
(void)name;
#else
pthread_setname_np(pthread_self(), 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) {
#if 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
}
}
} // namespace Common

View File

@@ -106,7 +106,7 @@ enum class ThreadPriority : u32 {
};
void SetCurrentThreadPriority(ThreadPriority new_priority);
void SetCurrentThreadName(const char* name);
void PinCurrentThreadToPerformanceCore(size_t core_id);
} // namespace Common

View File

@@ -7,6 +7,7 @@
#include "common/fiber.h"
#include "common/scope_exit.h"
#include "common/thread.h"
#include "common/settings.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/cpu_manager.h"
@@ -25,11 +26,8 @@ CpuManager::~CpuManager() = default;
void CpuManager::Initialize() {
num_cores = is_multicore ? Core::Hardware::NUM_CPU_CORES : 1;
gpu_barrier = std::make_unique<Common::Barrier>(num_cores + 1);
for (std::size_t core = 0; core < num_cores; core++) {
core_data[core].host_thread =
std::jthread([this, core](std::stop_token token) { RunThread(token, core); });
}
for (std::size_t core = 0; core < num_cores; core++)
core_data[core].host_thread = std::jthread([this, core](std::stop_token token) { RunThread(token, core); });
}
void CpuManager::Shutdown() {
@@ -188,14 +186,10 @@ void CpuManager::ShutdownThread() {
void CpuManager::RunThread(std::stop_token token, std::size_t core) {
/// Initialization
system.RegisterCoreThread(core);
std::string name;
if (is_multicore) {
name = "CPUCore_" + std::to_string(core);
} else {
name = "CPUThread";
}
std::string name = is_multicore ? ("CPUCore_" + std::to_string(core)) : std::string{"CPUThread"};
Common::SetCurrentThreadName(name.c_str());
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
Common::PinCurrentThreadToPerformanceCore(core);
auto& data = core_data[core];
data.host_context = Common::Fiber::ThreadToFiber();

View File

@@ -1,11 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <mutex>
#include <ranges>
#include "common/assert.h"
#include "core/core.h"
@@ -21,61 +17,57 @@ GlobalSchedulerContext::GlobalSchedulerContext(KernelCore& kernel)
GlobalSchedulerContext::~GlobalSchedulerContext() = default;
/// @brief Adds a new thread to the scheduler
void GlobalSchedulerContext::AddThread(KThread* thread) noexcept {
void GlobalSchedulerContext::AddThread(KThread* thread) {
std::scoped_lock lock{m_global_list_guard};
m_thread_list.push_back(thread);
}
/// @brief Removes a thread from the scheduler
void GlobalSchedulerContext::RemoveThread(KThread* thread) noexcept {
void GlobalSchedulerContext::RemoveThread(KThread* thread) {
std::scoped_lock lock{m_global_list_guard};
m_thread_list.erase(std::ranges::find(m_thread_list, thread));
std::erase(m_thread_list, thread);
}
/// @brief Rotates the scheduling queues of threads at a preemption priority
/// and then does some core rebalancing. Preemption priorities can be found
/// in the array 'preemption_priorities'.
/// @note This operation happens every 10ms.
void GlobalSchedulerContext::PreemptThreads() noexcept {
void GlobalSchedulerContext::PreemptThreads() {
// The priority levels at which the global scheduler preempts threads every 10 ms. They are
// ordered from Core 0 to Core 3.
static constexpr std::array<u32, Core::Hardware::NUM_CPU_CORES> per_core{
static constexpr std::array<u32, Core::Hardware::NUM_CPU_CORES> preemption_priorities{
59,
59,
59,
63,
};
ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel));
for (u32 core_id = 0; core_id < per_core.size(); core_id++)
KScheduler::RotateScheduledQueue(m_kernel, core_id, per_core[core_id]);
for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
const u32 priority = preemption_priorities[core_id];
KScheduler::RotateScheduledQueue(m_kernel, core_id, priority);
}
}
/// @brief Returns true if the global scheduler lock is acquired
bool GlobalSchedulerContext::IsLocked() const noexcept {
bool GlobalSchedulerContext::IsLocked() const {
return m_scheduler_lock.IsLockedByCurrentThread();
}
void GlobalSchedulerContext::RegisterDummyThreadForWakeup(KThread* thread) noexcept {
void GlobalSchedulerContext::RegisterDummyThreadForWakeup(KThread* thread) {
ASSERT(this->IsLocked());
m_woken_dummy_threads.push_back(thread);
m_woken_dummy_threads.insert(thread);
}
void GlobalSchedulerContext::UnregisterDummyThreadForWakeup(KThread* thread) noexcept {
void GlobalSchedulerContext::UnregisterDummyThreadForWakeup(KThread* thread) {
ASSERT(this->IsLocked());
if(auto it = std::ranges::find(m_woken_dummy_threads, thread); it != m_woken_dummy_threads.end()) {
*it = m_woken_dummy_threads.back();
m_woken_dummy_threads.pop_back();
}
m_woken_dummy_threads.erase(thread);
}
void GlobalSchedulerContext::WakeupWaitingDummyThreads() noexcept {
void GlobalSchedulerContext::WakeupWaitingDummyThreads() {
ASSERT(this->IsLocked());
if (m_woken_dummy_threads.size() > 0) {
for (auto* thread : m_woken_dummy_threads)
thread->DummyThreadEndWait();
m_woken_dummy_threads.clear();
for (auto* thread : m_woken_dummy_threads) {
thread->DummyThreadEndWait();
}
m_woken_dummy_threads.clear();
}
} // namespace Kernel

View File

@@ -1,13 +1,11 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <atomic>
#include <boost/container/small_vector.hpp>
#include <set>
#include <vector>
#include "common/common_types.h"
#include "core/hardware_properties.h"
@@ -33,42 +31,59 @@ class GlobalSchedulerContext final {
friend class KScheduler;
public:
static constexpr size_t MAX_THREADS = 256;
using LockType = KAbstractSchedulerLock<KScheduler>;
using ThreadList = boost::container::small_vector<KThread*, MAX_THREADS>;
explicit GlobalSchedulerContext(KernelCore& kernel);
~GlobalSchedulerContext();
/// @brief Returns a list of all threads managed by the scheduler
/// Adds a new thread to the scheduler
void AddThread(KThread* thread);
/// Removes a thread from the scheduler
void RemoveThread(KThread* thread);
/// Returns a list of all threads managed by the scheduler
/// This is only safe to iterate while holding the scheduler lock
ThreadList const& GetThreadList() const noexcept {
const std::vector<KThread*>& GetThreadList() const {
return m_thread_list;
}
LockType& SchedulerLock() noexcept {
/**
* Rotates the scheduling queues of threads at a preemption priority and then does
* some core rebalancing. Preemption priorities can be found in the array
* 'preemption_priorities'.
*
* @note This operation happens every 10ms.
*/
void PreemptThreads();
/// Returns true if the global scheduler lock is acquired
bool IsLocked() const;
void UnregisterDummyThreadForWakeup(KThread* thread);
void RegisterDummyThreadForWakeup(KThread* thread);
void WakeupWaitingDummyThreads();
LockType& SchedulerLock() {
return m_scheduler_lock;
}
void AddThread(KThread* thread) noexcept;
void RemoveThread(KThread* thread) noexcept;
void PreemptThreads() noexcept;
bool IsLocked() const noexcept;
void UnregisterDummyThreadForWakeup(KThread* thread) noexcept;
void RegisterDummyThreadForWakeup(KThread* thread) noexcept;
void WakeupWaitingDummyThreads() noexcept;
private:
friend class KScopedSchedulerLock;
friend class KScopedSchedulerLockAndSleep;
KernelCore& m_kernel;
std::atomic_bool m_scheduler_update_needed{};
KSchedulerPriorityQueue m_priority_queue;
LockType m_scheduler_lock;
std::mutex m_global_list_guard;
/// Lists dummy threads pending wakeup on lock release
ThreadList m_woken_dummy_threads;
std::set<KThread*> m_woken_dummy_threads;
/// Lists all thread ids that aren't deleted/etc.
ThreadList m_thread_list;
std::vector<KThread*> m_thread_list;
std::mutex m_global_list_guard;
};
} // namespace Kernel

View File

@@ -527,27 +527,35 @@ void KScheduler::ClearPreviousThread(KernelCore& kernel, KThread* thread) {
void KScheduler::OnThreadStateChanged(KernelCore& kernel, KThread* thread, ThreadState old_state) {
ASSERT(IsSchedulerLockedByCurrentThread(kernel));
// Check if the state has changed, because if it hasn't there's nothing to do.
if (const ThreadState cur_state = thread->GetRawState(); cur_state != old_state) {
// Update the priority queues.
if (old_state == ThreadState::Runnable) {
// If we were previously runnable, then we're not runnable now, and we should remove.
GetPriorityQueue(kernel).Remove(thread);
IncrementScheduledCount(thread);
SetSchedulerUpdateNeeded(kernel);
const ThreadState cur_state = thread->GetRawState();
if (cur_state == old_state) {
return;
}
// Update the priority queues.
if (old_state == ThreadState::Runnable) {
// If we were previously runnable, then we're not runnable now, and we should remove.
GetPriorityQueue(kernel).Remove(thread);
IncrementScheduledCount(thread);
SetSchedulerUpdateNeeded(kernel);
if (thread->IsDummyThread()) {
// HACK: if this is a dummy thread, it should no longer wake up when the
// scheduler lock is released.
if (thread->IsDummyThread())
kernel.GlobalSchedulerContext().UnregisterDummyThreadForWakeup(thread);
} else if (cur_state == ThreadState::Runnable) {
// If we're now runnable, then we weren't previously, and we should add.
GetPriorityQueue(kernel).PushBack(thread);
IncrementScheduledCount(thread);
SetSchedulerUpdateNeeded(kernel);
kernel.GlobalSchedulerContext().UnregisterDummyThreadForWakeup(thread);
}
} else if (cur_state == ThreadState::Runnable) {
// If we're now runnable, then we weren't previously, and we should add.
GetPriorityQueue(kernel).PushBack(thread);
IncrementScheduledCount(thread);
SetSchedulerUpdateNeeded(kernel);
if (thread->IsDummyThread()) {
// HACK: if this is a dummy thread, it should wake up when the scheduler
// lock is released.
if (thread->IsDummyThread())
kernel.GlobalSchedulerContext().RegisterDummyThreadForWakeup(thread);
kernel.GlobalSchedulerContext().RegisterDummyThreadForWakeup(thread);
}
}
}