scheduler: Move mode specific logic to separate files

This commit is contained in:
Pawel Dziepak
2013-11-20 09:46:59 +01:00
parent e2ff9a2865
commit 9c2e74da04
8 changed files with 747 additions and 598 deletions
+1
View File
@@ -1,4 +1,5 @@
/*
* Copyright 2013, Paweł Dziepak, [email protected].
* Copyright 2008-2011, Ingo Weinhold, [email protected].
* Copyright 2005-2010, Axel Dörfler, [email protected].
* Distributed under the terms of the MIT License.
+4 -1
View File
@@ -6,12 +6,15 @@
#define _KERNEL_LOAD_TRACKING_H
#include <OS.h>
const int32 kMaxLoad = 1000;
const bigtime_t kLoadMeasureInterval = 50000;
const bigtime_t kIntervalInaccuracy = kLoadMeasureInterval / 4;
static int32
static inline int32
compute_load(bigtime_t& measureTime, bigtime_t& measureActiveTime, int32& load)
{
bigtime_t now = system_time();
+2
View File
@@ -62,6 +62,8 @@ KernelMergeObject kernel_core.o :
user_mutex.cpp
# scheduler
low_latency.cpp
power_saving.cpp
scheduler.cpp
scheduler_tracing.cpp
scheduling_analysis.cpp
+154
View File
@@ -0,0 +1,154 @@
/*
* Copyright 2013, Paweł Dziepak, [email protected].
* Distributed under the terms of the MIT License.
*/
#include <util/AutoLock.h>
#include "scheduler_common.h"
#include "scheduler_modes.h"
using namespace Scheduler;
static void
switch_to_mode(void)
{
}
static bool
has_cache_expired(Thread* thread)
{
ASSERT(!gSingleCore);
if (thread_is_idle_thread(thread))
return false;
scheduler_thread_data* schedulerThreadData = thread->scheduler_data;
ASSERT(schedulerThreadData->previous_core >= 0);
CoreEntry* coreEntry = &gCoreEntries[schedulerThreadData->previous_core];
return atomic_get64(&coreEntry->fActiveTime)
- schedulerThreadData->went_sleep_active > kCacheExpire;
}
static int32
choose_core(Thread* thread)
{
CoreEntry* entry;
if (gIdlePackageList->Last() != NULL) {
// wake new package
PackageEntry* package = gIdlePackageList->Last();
entry = package->fIdleCores.Last();
} else if (gPackageUsageHeap->PeekMaximum() != NULL) {
// wake new core
PackageEntry* package = gPackageUsageHeap->PeekMaximum();
entry = package->fIdleCores.Last();
} else {
// no idle cores, use least occupied core
entry = gCoreLoadHeap->PeekMinimum();
if (entry == NULL)
entry = gCoreHighLoadHeap->PeekMinimum();
}
ASSERT(entry != NULL);
return entry->fCoreID;
}
static bool
should_rebalance(Thread* thread)
{
scheduler_thread_data* schedulerThreadData = thread->scheduler_data;
ASSERT(schedulerThreadData->previous_core >= 0);
CoreEntry* coreEntry = &gCoreEntries[schedulerThreadData->previous_core];
// If the thread produces more than 50% of the load, leave it here. In
// such situation it is better to move other threads away.
if (schedulerThreadData->load >= coreEntry->fLoad / 2)
return false;
// If there is high load on this core but this thread does not contribute
// significantly consider giving it to someone less busy.
if (coreEntry->fLoad > kHighLoad) {
SpinLocker coreLocker(gCoreHeapsLock);
CoreEntry* other = gCoreLoadHeap->PeekMinimum();
if (other != NULL && coreEntry->fLoad - other->fLoad >= kLoadDifference)
return true;
}
// No cpu bound threads - the situation is quite good. Make sure it
// won't get much worse...
SpinLocker coreLocker(gCoreHeapsLock);
CoreEntry* other = gCoreLoadHeap->PeekMinimum();
if (other == NULL)
other = gCoreHighLoadHeap->PeekMinimum();
return coreEntry->fLoad - other->fLoad >= kLoadDifference * 2;
}
static void
rebalance_irqs(bool idle)
{
if (idle)
return;
cpu_ent* cpu = get_cpu_struct();
SpinLocker locker(cpu->irqs_lock);
irq_assignment* chosen = NULL;
irq_assignment* irq = (irq_assignment*)list_get_first_item(&cpu->irqs);
int32 totalLoad = 0;
while (irq != NULL) {
if (chosen == NULL || chosen->load < irq->load)
chosen = irq;
totalLoad += irq->load;
irq = (irq_assignment*)list_get_next_item(&cpu->irqs, irq);
}
locker.Unlock();
if (chosen == NULL || totalLoad < kLowLoad)
return;
SpinLocker coreLocker(gCoreHeapsLock);
CoreEntry* other = gCoreLoadHeap->PeekMinimum();
if (other == NULL)
other = gCoreHighLoadHeap->PeekMinimum();
coreLocker.Unlock();
ASSERT(other != NULL);
int32 thigCore = gCPUToCore[smp_get_current_cpu()];
if (other->fCoreID == thigCore)
return;
if (other->fLoad + kLoadDifference >= gCoreEntries[thigCore].fLoad)
return;
coreLocker.Lock();
gCPUPriorityHeaps[other->fCoreID].PeekMinimum();
}
scheduler_mode_operations gSchedulerLowLatencyMode = {
"low latency",
true,
switch_to_mode,
has_cache_expired,
choose_core,
should_rebalance,
rebalance_irqs,
};
@@ -0,0 +1,164 @@
/*
* Copyright 2013, Paweł Dziepak, pdziepak@quarnos.org.
* Distributed under the terms of the MIT License.
*/
#include <util/AutoLock.h>
#include "scheduler_common.h"
#include "scheduler_modes.h"
using namespace Scheduler;
static bigtime_t sDisableSmallTaskPacking;
static int32 sSmallTaskCore;
static bool
has_cache_expired(Thread* thread)
{
ASSERT(!gSingleCore);
if (thread_is_idle_thread(thread))
return false;
scheduler_thread_data* schedulerThreadData = thread->scheduler_data;
ASSERT(schedulerThreadData->previous_core >= 0);
CoreEntry* coreEntry = &gCoreEntries[schedulerThreadData->previous_core];
return system_time() - schedulerThreadData->went_sleep > kCacheExpire;
}
static inline bool
is_small_task_packing_enabled(void)
{
if (sDisableSmallTaskPacking == -1)
return false;
return sDisableSmallTaskPacking < system_time();
}
static inline void
disable_small_task_packing(void)
{
ASSERT(!gSingleCore);
ASSERT(is_small_task_packing_enabled());
ASSERT(sSmallTaskCore == gCPUToCore[smp_get_current_cpu()]);
sDisableSmallTaskPacking = system_time() + kThreadQuantum * 100;
sSmallTaskCore = -1;
}
static inline bool
is_task_small(Thread* thread)
{
return thread->scheduler_data->load <= 200;
}
static void
switch_to_mode(void)
{
sDisableSmallTaskPacking = -1;
sSmallTaskCore = -1;
}
static int32
choose_core(Thread* thread)
{
CoreEntry* entry;
if (is_small_task_packing_enabled() && is_task_small(thread)
&& gCoreLoadHeap->PeekMaximum() != NULL) {
// try to pack all threads on one core
if (sSmallTaskCore < 0)
sSmallTaskCore = gCoreLoadHeap->PeekMaximum()->fCoreID;
entry = &gCoreEntries[sSmallTaskCore];
} else if (gCoreLoadHeap->PeekMinimum() != NULL) {
// run immediately on already woken core
entry = gCoreLoadHeap->PeekMinimum();
} else if (gPackageUsageHeap->PeekMinimum() != NULL) {
// wake new core
PackageEntry* package = gPackageUsageHeap->PeekMinimum();
entry = package->fIdleCores.Last();
} else if (gIdlePackageList->Last() != NULL) {
// wake new package
PackageEntry* package = gIdlePackageList->Last();
entry = package->fIdleCores.Last();
} else {
// no idle cores, use least occupied core
entry = gCoreLoadHeap->PeekMinimum();
if (entry == NULL)
entry = gCoreHighLoadHeap->PeekMinimum();
}
ASSERT(entry != NULL);
return entry->fCoreID;
}
static bool
should_rebalance(Thread* thread)
{
ASSERT(!gSingleCore);
if (thread_is_idle_thread(thread))
return false;
scheduler_thread_data* schedulerThreadData = thread->scheduler_data;
ASSERT(schedulerThreadData->previous_core >= 0);
int32 core = schedulerThreadData->previous_core;
CoreEntry* coreEntry = &gCoreEntries[core];
// If the thread produces more than 50% of the load, leave it here. In
// such situation it is better to move other threads away.
// Unless we are trying to pack small tasks here, in such case get rid
// of CPU hungry thread and continue packing.
if (schedulerThreadData->load >= coreEntry->fLoad / 2)
return is_small_task_packing_enabled() && sSmallTaskCore == core;
// All cores try to give us small tasks, check whether we have enough.
if (is_small_task_packing_enabled() && sSmallTaskCore == core) {
if (coreEntry->fLoad > kHighLoad) {
if (!is_task_small(thread))
return true;
} else if (coreEntry->fLoad > kVeryHighLoad)
disable_small_task_packing();
}
// Try small task packing.
if (is_small_task_packing_enabled() && is_task_small(thread))
return sSmallTaskCore != core;
// No cpu bound threads - the situation is quite good. Make sure it
// won't get much worse...
SpinLocker coreLocker(gCoreHeapsLock);
CoreEntry* other = gCoreLoadHeap->PeekMinimum();
if (other == NULL)
other = gCoreHighLoadHeap->PeekMinimum();
return coreEntry->fLoad - other->fLoad >= kLoadDifference;
}
scheduler_mode_operations gSchedulerPowerSavingMode = {
"power saving",
false,
switch_to_mode,
has_cache_expired,
choose_core,
should_rebalance,
NULL,
};
File diff suppressed because it is too large Load Diff
@@ -1,4 +1,5 @@
/*
* Copyright 2013, Paweł Dziepak, pdziepak@quarnos.org.
* Copyright 2011, Ingo Weinhold, ingo_weinhold@gmx.de.
* Distributed under the terms of the MIT License.
*/
@@ -6,9 +7,167 @@
#define KERNEL_SCHEDULER_COMMON_H
#include <debug.h>
#include <kscheduler.h>
#include <load_tracking.h>
#include <smp.h>
#include <thread.h>
#include <user_debugger.h>
#include <util/MinMaxHeap.h>
#include "RunQueue.h"
#define CACHE_LINE_ALIGN __attribute__((aligned(64)))
//#define TRACE_SCHEDULER
#ifdef TRACE_SCHEDULER
# define TRACE(...) dprintf_no_syslog(__VA_ARGS__)
#else
# define TRACE(...) do { } while (false)
#endif
namespace Scheduler {
const bigtime_t kThreadQuantum = 1000;
const bigtime_t kMinThreadQuantum = 3000;
const bigtime_t kMaxThreadQuantum = 10000;
const bigtime_t kMinimalWaitTime = kThreadQuantum / 4;
const bigtime_t kCacheExpire = 100000;
const int kTargetLoad = kMaxLoad * 55 / 100;
const int kHighLoad = kMaxLoad * 70 / 100;
const int kVeryHighLoad = (kMaxLoad + kHighLoad) / 2;
const int kLoadDifference = kMaxLoad * 20 / 100;
const int kLowLoad = kLoadDifference / 2;
extern bool gSingleCore;
// Heaps in sCPUPriorityHeaps are used for load balancing on a core the logical
// processors in the heap belong to. Since there are no cache affinity issues
// at this level and the run queue is shared among all logical processors on
// the core the only real concern is to make lower priority threads give way to
// the higher priority threads.
struct CPUEntry : public MinMaxHeapLinkImpl<CPUEntry, int32> {
CPUEntry();
int32 fCPUNumber;
int32 fPriority;
bigtime_t fMeasureActiveTime;
bigtime_t fMeasureTime;
int32 fLoad;
} CACHE_LINE_ALIGN;
typedef MinMaxHeap<CPUEntry, int32> CPUHeap CACHE_LINE_ALIGN;
extern CPUEntry* gCPUEntries;
extern CPUHeap* gCPUPriorityHeaps;
struct CoreEntry : public MinMaxHeapLinkImpl<CoreEntry, int32>,
DoublyLinkedListLinkImpl<CoreEntry> {
CoreEntry();
int32 fCoreID;
spinlock fLock;
bigtime_t fStartedBottom;
bigtime_t fReachedBottom;
bigtime_t fStartedIdle;
bigtime_t fReachedIdle;
bigtime_t fActiveTime;
int32 fLoad;
} CACHE_LINE_ALIGN;
typedef MinMaxHeap<CoreEntry, int32> CoreLoadHeap;
extern CoreEntry* gCoreEntries;
extern CoreLoadHeap* gCoreLoadHeap;
extern CoreLoadHeap* gCoreHighLoadHeap;
extern spinlock gCoreHeapsLock;
// sPackageUsageHeap is used to decide which core should be woken up from the
// idle state. When aiming for performance we should use as many packages as
// possible with as little cores active in each package as possible (so that the
// package can enter any boost mode if it has one and the active core have more
// of the shared cache for themselves. If power saving is the main priority we
// should keep active cores on as little packages as possible (so that other
// packages can go to the deep state of sleep). The heap stores only packages
// with at least one core active and one core idle. The packages with all cores
// idle are stored in sPackageIdleList (in LIFO manner).
struct PackageEntry : public MinMaxHeapLinkImpl<PackageEntry, int32>,
DoublyLinkedListLinkImpl<PackageEntry> {
PackageEntry();
int32 fPackageID;
DoublyLinkedList<CoreEntry> fIdleCores;
int32 fIdleCoreCount;
int32 fCoreCount;
} CACHE_LINE_ALIGN;
typedef MinMaxHeap<PackageEntry, int32> PackageHeap;
typedef DoublyLinkedList<PackageEntry> IdlePackageList;
extern PackageEntry* gPackageEntries;
extern PackageHeap* gPackageUsageHeap;
extern IdlePackageList* gIdlePackageList;
extern spinlock gIdlePackageLock;
// The run queues. Holds the threads ready to run ordered by priority.
// One queue per schedulable target per core. Additionally, each
// logical processor has its sPinnedRunQueues used for scheduling
// pinned threads.
typedef RunQueue<Thread, THREAD_MAX_SET_PRIORITY> CACHE_LINE_ALIGN
ThreadRunQueue;
extern ThreadRunQueue* gRunQueues;
extern ThreadRunQueue* gPinnedRunQueues;
extern int32 gRunQueueCount;
// Since CPU IDs used internally by the kernel bear no relation to the actual
// CPU topology the following arrays are used to efficiently get the core
// and the package that CPU in question belongs to.
extern int32* gCPUToCore;
extern int32* gCPUToPackage;
} // namespace Scheduler
struct scheduler_thread_data {
inline scheduler_thread_data();
void Init();
int32 priority_penalty;
int32 additional_penalty;
bool lost_cpu;
bool cpu_bound;
bigtime_t time_left;
bigtime_t stolen_time;
bigtime_t quantum_start;
bigtime_t measure_active_time;
bigtime_t measure_time;
int32 load;
bigtime_t went_sleep;
bigtime_t went_sleep_active;
int32 previous_core;
bool enqueued;
};
/*! Switches the currently running thread.
@@ -0,0 +1,29 @@
/*
* Copyright 2013, Paweł Dziepak, pdziepak@quarnos.org.
* Distributed under the terms of the MIT License.
*/
#ifndef KERNEL_SCHEDULER_MODES_H
#define KERNEL_SCHEDULER_MODES_H
#include <kscheduler.h>
#include <thread_types.h>
struct scheduler_mode_operations {
const char* name;
bool avoid_boost;
void (*switch_to_mode)(void);
bool (*has_cache_expired)(Thread* thread);
int32 (*choose_core)(Thread* thread);
bool (*should_rebalance)(Thread* thread);
void (*rebalance_irqs)(bool idle);
};
extern struct scheduler_mode_operations gSchedulerLowLatencyMode;
extern struct scheduler_mode_operations gSchedulerPowerSavingMode;
#endif // KERNEL_SCHEDULER_MODES_H