scheduler: Add explanations for YieldWith and WithoutLoadBalancing
This commit is contained in:
		| @@ -6,7 +6,6 @@ | ||||
|  | ||||
| #include <array> | ||||
| #include <deque> | ||||
| #include <functional> | ||||
| #include <boost/range/algorithm_ext/erase.hpp> | ||||
|  | ||||
| namespace Common { | ||||
| @@ -50,7 +49,8 @@ struct ThreadQueueList { | ||||
|         return T(); | ||||
|     } | ||||
|  | ||||
|     T get_first_filter(std::function<bool(T)> filter) const { | ||||
|     template <typename UnaryPredicate> | ||||
|     T get_first_filter(UnaryPredicate filter) const { | ||||
|         const Queue* cur = first; | ||||
|         while (cur != nullptr) { | ||||
|             if (!cur->data.empty()) { | ||||
|   | ||||
| @@ -9,6 +9,7 @@ | ||||
| #include "common/logging/log.h" | ||||
| #include "core/arm/arm_interface.h" | ||||
| #include "core/core.h" | ||||
| #include "core/core_cpu.h" | ||||
| #include "core/core_timing.h" | ||||
| #include "core/hle/kernel/kernel.h" | ||||
| #include "core/hle/kernel/process.h" | ||||
| @@ -169,7 +170,7 @@ void Scheduler::UnscheduleThread(Thread* thread, u32 priority) { | ||||
|     ready_queue.remove(priority, thread); | ||||
| } | ||||
|  | ||||
| void Scheduler::RescheduleThread(Thread* thread, u32 priority) { | ||||
| void Scheduler::MoveThreadToBackOfPriorityQueue(Thread* thread, u32 priority) { | ||||
|     std::lock_guard<std::mutex> lock(scheduler_mutex); | ||||
|  | ||||
|     // Thread is not in queue | ||||
| @@ -189,12 +190,64 @@ void Scheduler::SetThreadPriority(Thread* thread, u32 priority) { | ||||
|         ready_queue.prepare(priority); | ||||
| } | ||||
|  | ||||
| Thread* Scheduler::GetNextSuggestedThread(u32 core) { | ||||
| Thread* Scheduler::GetNextSuggestedThread(u32 core) const { | ||||
|     std::lock_guard<std::mutex> lock(scheduler_mutex); | ||||
|  | ||||
|     const auto mask = 1 << core; | ||||
|     const u32 mask = 1U << core; | ||||
|     return ready_queue.get_first_filter( | ||||
|         [&mask](Thread* thread) { return (thread->GetAffinityMask() & mask) != 0; }); | ||||
|         [mask](Thread const* thread) { return (thread->GetAffinityMask() & mask) != 0; }); | ||||
| } | ||||
|  | ||||
| void Scheduler::YieldWithoutLoadBalancing(Thread* thread) { | ||||
|     ASSERT(thread != nullptr); | ||||
|     // Avoid yielding if the thread isn't even running. | ||||
|     ASSERT(thread->GetStatus() == ThreadStatus::Running); | ||||
|  | ||||
|     // Sanity check that the priority is valid | ||||
|     ASSERT(thread->GetPriority() < THREADPRIO_COUNT); | ||||
|  | ||||
|     // Yield this thread | ||||
|     MoveThreadToBackOfPriorityQueue(thread, thread->GetPriority()); | ||||
|     Reschedule(); | ||||
| } | ||||
|  | ||||
| void Scheduler::YieldWithLoadBalancing(Thread* thread) { | ||||
|     ASSERT(thread != nullptr); | ||||
|     const auto priority = thread->GetPriority(); | ||||
|     const auto core = static_cast<u32>(thread->GetProcessorID()); | ||||
|  | ||||
|     // Avoid yielding if the thread isn't even running. | ||||
|     ASSERT(thread->GetStatus() == ThreadStatus::Running); | ||||
|  | ||||
|     // Sanity check that the priority is valid | ||||
|     ASSERT(priority < THREADPRIO_COUNT); | ||||
|  | ||||
|     // Reschedule thread to end of queue. | ||||
|     MoveThreadToBackOfPriorityQueue(thread, priority); | ||||
|  | ||||
|     Thread* suggested_thread = nullptr; | ||||
|  | ||||
|     // Search through all of the cpu cores (except this one) for a suggested thread. | ||||
|     // Take the first non-nullptr one | ||||
|     for (unsigned cur_core = 0; cur_core < Core::NUM_CPU_CORES; ++cur_core) { | ||||
|         if (cur_core == core) | ||||
|             continue; | ||||
|  | ||||
|         const auto res = | ||||
|             Core::System::GetInstance().CpuCore(cur_core).Scheduler().GetNextSuggestedThread(core); | ||||
|         if (res != nullptr) { | ||||
|             suggested_thread = res; | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // If a suggested thread was found, queue that for this core | ||||
|     if (suggested_thread != nullptr) | ||||
|         suggested_thread->ChangeCore(core, suggested_thread->GetAffinityMask()); | ||||
| } | ||||
|  | ||||
| void Scheduler::YieldAndWaitForLoadBalancing(Thread* thread) { | ||||
|     UNIMPLEMENTED_MSG("Wait for load balancing thread yield type is not implemented!"); | ||||
| } | ||||
|  | ||||
| } // namespace Kernel | ||||
|   | ||||
| @@ -49,13 +49,79 @@ public: | ||||
|     void UnscheduleThread(Thread* thread, u32 priority); | ||||
|  | ||||
|     /// Moves a thread to the back of the current priority queue | ||||
|     void RescheduleThread(Thread* thread, u32 priority); | ||||
|     void MoveThreadToBackOfPriorityQueue(Thread* thread, u32 priority); | ||||
|  | ||||
|     /// Sets the priority of a thread in the scheduler | ||||
|     void SetThreadPriority(Thread* thread, u32 priority); | ||||
|  | ||||
|     /// Gets the next suggested thread for load balancing | ||||
|     Thread* GetNextSuggestedThread(u32 core); | ||||
|     Thread* GetNextSuggestedThread(u32 core) const; | ||||
|  | ||||
|     /** | ||||
|      * YieldWithoutLoadBalancing -- analogous to normal yield on a system | ||||
|      * Moves the thread to the end of the ready queue for its priority, and then reschedules the | ||||
|      * system to the new head of the queue. | ||||
|      * | ||||
|      * Example (Single Core -- but can be extrapolated to multi): | ||||
|      * ready_queue[prio=0]: ThreadA, ThreadB, ThreadC (->exec order->) | ||||
|      * Currently Running: ThreadR | ||||
|      * | ||||
|      * ThreadR calls YieldWithoutLoadBalancing | ||||
|      * | ||||
|      * ThreadR is moved to the end of ready_queue[prio=0]: | ||||
|      * ready_queue[prio=0]: ThreadA, ThreadB, ThreadC, ThreadR (->exec order->) | ||||
|      * Currently Running: Nothing | ||||
|      * | ||||
|      * System is rescheduled (ThreadA is popped off of queue): | ||||
|      * ready_queue[prio=0]: ThreadB, ThreadC, ThreadR (->exec order->) | ||||
|      * Currently Running: ThreadA | ||||
|      * | ||||
|      * If the queue is empty at time of call, no yielding occurs. This does not cross between cores | ||||
|      * or priorities at all. | ||||
|      */ | ||||
|     void YieldWithoutLoadBalancing(Thread* thread); | ||||
|  | ||||
|     /** | ||||
|      * YieldWithLoadBalancing -- yield but with better selection of the new running thread | ||||
|      * Moves the current thread to the end of the ready queue for its priority, then selects a | ||||
|      * 'suggested thread' (a thread on a different core that could run on this core) from the | ||||
|      * scheduler, changes its core, and reschedules the current core to that thread. | ||||
|      * | ||||
|      * Example (Dual Core -- can be extrapolated to Quad Core, this is just normal yield if it were | ||||
|      * single core): | ||||
|      * ready_queue[core=0][prio=0]: ThreadA, ThreadB (affinities not pictured as irrelevant | ||||
|      * ready_queue[core=1][prio=0]: ThreadC[affinity=both], ThreadD[affinity=core1only] | ||||
|      * Currently Running: ThreadQ on Core 0 || ThreadP on Core 1 | ||||
|      * | ||||
|      * ThreadQ calls YieldWithLoadBalancing | ||||
|      * | ||||
|      * ThreadQ is moved to the end of ready_queue[core=0][prio=0]: | ||||
|      * ready_queue[core=0][prio=0]: ThreadA, ThreadB | ||||
|      * ready_queue[core=1][prio=0]: ThreadC[affinity=both], ThreadD[affinity=core1only] | ||||
|      * Currently Running: ThreadQ on Core 0 || ThreadP on Core 1 | ||||
|      * | ||||
|      * A list of suggested threads for each core is compiled | ||||
|      * Suggested Threads: {ThreadC on Core 1} | ||||
|      * If this were quad core (as the switch is), there could be between 0 and 3 threads in this | ||||
|      * list. If there are more than one, the thread is selected by highest prio. | ||||
|      * | ||||
|      * ThreadC is core changed to Core 0: | ||||
|      * ready_queue[core=0][prio=0]: ThreadC, ThreadA, ThreadB, ThreadQ | ||||
|      * ready_queue[core=1][prio=0]: ThreadD | ||||
|      * Currently Running: None on Core 0 || ThreadP on Core 1 | ||||
|      * | ||||
|      * System is rescheduled (ThreadC is popped off of queue): | ||||
|      * ready_queue[core=0][prio=0]: ThreadA, ThreadB, ThreadQ | ||||
|      * ready_queue[core=1][prio=0]: ThreadD | ||||
|      * Currently Running: ThreadC on Core 0 || ThreadP on Core 1 | ||||
|      * | ||||
|      * If no suggested threads can be found this will behave just as normal yield. If there are | ||||
|      * multiple candidates for the suggested thread on a core, the highest prio is taken. | ||||
|      */ | ||||
|     void YieldWithLoadBalancing(Thread* thread); | ||||
|  | ||||
|     /// Currently unknown -- asserts as unimplemented on call | ||||
|     void YieldAndWaitForLoadBalancing(Thread* thread); | ||||
|  | ||||
|     /// Returns a list of all threads managed by the scheduler | ||||
|     const std::vector<SharedPtr<Thread>>& GetThreadList() const { | ||||
|   | ||||
| @@ -965,16 +965,23 @@ static void SleepThread(s64 nanoseconds) { | ||||
|     if (!Core::System::GetInstance().CurrentScheduler().HaveReadyThreads()) | ||||
|         return; | ||||
|  | ||||
|     enum class SleepType : s64 { | ||||
|         YieldWithoutLoadBalancing = 0, | ||||
|         YieldWithLoadBalancing = 1, | ||||
|         YieldAndWaitForLoadBalancing = 2, | ||||
|     }; | ||||
|  | ||||
|     if (nanoseconds <= 0) { | ||||
|         switch (nanoseconds) { | ||||
|         case 0: | ||||
|             GetCurrentThread()->YieldNormal(); | ||||
|         auto& scheduler{Core::System::GetInstance().CurrentScheduler()}; | ||||
|         switch (static_cast<SleepType>(nanoseconds)) { | ||||
|         case SleepType::YieldWithoutLoadBalancing: | ||||
|             scheduler.YieldWithoutLoadBalancing(GetCurrentThread()); | ||||
|             break; | ||||
|         case -1: | ||||
|             GetCurrentThread()->YieldWithLoadBalancing(); | ||||
|         case SleepType::YieldWithLoadBalancing: | ||||
|             scheduler.YieldWithLoadBalancing(GetCurrentThread()); | ||||
|             break; | ||||
|         case -2: | ||||
|             GetCurrentThread()->YieldAndWaitForLoadBalancing(); | ||||
|         case SleepType::YieldAndWaitForLoadBalancing: | ||||
|             scheduler.YieldAndWaitForLoadBalancing(GetCurrentThread()); | ||||
|             break; | ||||
|         default: | ||||
|             UNREACHABLE_MSG( | ||||
|   | ||||
| @@ -388,66 +388,6 @@ bool Thread::InvokeWakeupCallback(ThreadWakeupReason reason, SharedPtr<Thread> t | ||||
|     return wakeup_callback(reason, std::move(thread), std::move(object), index); | ||||
| } | ||||
|  | ||||
| void Thread::YieldNormal() { | ||||
|     // Avoid yielding if the thread isn't even running. | ||||
|     if (status != ThreadStatus::Running) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     if (nominal_priority < THREADPRIO_COUNT) { | ||||
|         scheduler->RescheduleThread(this, nominal_priority); | ||||
|         scheduler->Reschedule(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void Thread::YieldWithLoadBalancing() { | ||||
|     auto priority = nominal_priority; | ||||
|     auto core = processor_id; | ||||
|  | ||||
|     // Avoid yielding if the thread isn't even running. | ||||
|     if (status != ThreadStatus::Running) { | ||||
|         Core::System::GetInstance().CpuCore(processor_id).PrepareReschedule(); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     SharedPtr<Thread> next; | ||||
|     const auto& threads = scheduler->GetThreadList(); | ||||
|  | ||||
|     if (priority < THREADPRIO_COUNT) { | ||||
|         // Reschedule thread to end of queue. | ||||
|         scheduler->RescheduleThread(this, priority); | ||||
|  | ||||
|         const auto iter = std::find_if(threads.begin(), threads.end(), | ||||
|                                        [&priority](const SharedPtr<Thread>& thread) { | ||||
|                                            return thread->GetNominalPriority() == priority; | ||||
|                                        }); | ||||
|  | ||||
|         if (iter != threads.end()) | ||||
|             next = iter->get(); | ||||
|     } | ||||
|  | ||||
|     Thread* suggested_thread = nullptr; | ||||
|  | ||||
|     for (int i = 0; i < 4; ++i) { | ||||
|         if (i == core) | ||||
|             continue; | ||||
|  | ||||
|         const auto res = | ||||
|             Core::System::GetInstance().CpuCore(i).Scheduler().GetNextSuggestedThread(core); | ||||
|         if (res != nullptr) { | ||||
|             suggested_thread = res; | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     if (suggested_thread != nullptr) | ||||
|         suggested_thread->ChangeCore(core, suggested_thread->GetAffinityMask()); | ||||
| } | ||||
|  | ||||
| void Thread::YieldAndWaitForLoadBalancing() { | ||||
|     UNIMPLEMENTED_MSG("Wait for load balancing thread yield type is not implemented!"); | ||||
| } | ||||
|  | ||||
| //////////////////////////////////////////////////////////////////////////////////////////////////// | ||||
|  | ||||
| /** | ||||
|   | ||||
| @@ -371,10 +371,6 @@ public: | ||||
|         return affinity_mask; | ||||
|     } | ||||
|  | ||||
|     void YieldNormal(); | ||||
|     void YieldWithLoadBalancing(); | ||||
|     void YieldAndWaitForLoadBalancing(); | ||||
|  | ||||
| private: | ||||
|     explicit Thread(KernelCore& kernel); | ||||
|     ~Thread() override; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Zach Hilman
					Zach Hilman