diff --git a/src/vault/gear/load-controller.hpp b/src/vault/gear/load-controller.hpp index e515f4393..e70d2f5f5 100644 --- a/src/vault/gear/load-controller.hpp +++ b/src/vault/gear/load-controller.hpp @@ -31,6 +31,39 @@ ** are designed to withstand a short-term imbalance, expecting that general ** engine parametrisation will be adjusted based on moving averages. ** + ** # Principles for Engine Load Control + ** + ** Scheduling and dispatch of Activities are driven by active workers invoking + ** the Scheduler-Service to retrieve the next piece of work. While this scheme + ** ensures that the scarce resource (computation or IO capacity) is directed + ** towards the most urgent next task, achieving a smooth operation of the engine + ** without wasted capacity requires additionally to control the request cycles + ** of the workers, possibly removing excess capacity. Whenever a worker pulls + ** the next task, an assessment of the timing situation is conducted, and the + ** worker is placed into some partition of the overall available capacity, + ** to reflect the current load and demand. Workers are thus moved between + ** the segments, preferring to assign work to workers already in the active + ** segment, thereby allowing idle workers to be shut down after some time. + ** + ** The key element to decide upon the classification of a worker is the current + ** scheduling situation: are some Activities overdue? does the next Activity + ** to be considered reach far into the future? If there is immediately imminent + ** work, then capacity is kept around; otherwise the capacity can be considered + ** to be in excess for now. A worker not required right now can be sent into a + ** targeted sleep delay, in order to shift its capacity into a zone where it + ** will more likely be required. It is essential to apply some randomisation + ** on such capacity shifts, in order to achieve an even distribution of free + ** capacity and avoid contention between workers asking for new assignments. + ** + ** When a worker becomes available and is not needed at the moment, the first + ** thing to check is the time of the next approaching Activity; this worker + ** can then be directed close to this next task, which thereby has been tended + ** for and can be marked accordingly. Any further worker appearing meanwhile + ** can then be directed into the time zone _after_ the next approaching task. + ** Workers immediately returning from active work are always preferred for + ** assigning new tasks, while workers returning from idle state are typically + ** sent back into idle state, unless there is direct need for more capacity. + ** ** @see scheduler.hpp ** @see SchedulerStress_test ** @@ -59,7 +92,27 @@ namespace gear { // using util::isnil; // using std::string; - using std::chrono::microseconds; + + using lib::time::Time; + using lib::time::FSecs; + using lib::time::Offset; + using lib::time::Duration; + using std::chrono_literals::operator ""ms; + using std::chrono_literals::operator ""us; + + namespace { // Scheduler default config + + inline TimeValue + _uTicks (std::chrono::microseconds us) + { + return TimeValue{us.count()}; + } + + + Duration SLEEP_HORIZON{_uTicks (20ms)}; + Duration WORK_HORIZON {_uTicks ( 5ms)}; + Duration NOW_HORIZON {_uTicks (50us)}; + } /** @@ -104,15 +157,20 @@ namespace gear { ,IDLETIME ///< time to go to sleep }; - Capacity - classifyCapacity() const + /** classification of time horizon for scheduling */ + static Capacity + classifyCapacity (Offset off) { - UNIMPLEMENTED ("establish a categorisation for available capacity"); + if (off > SLEEP_HORIZON) return IDLETIME; + if (off > WORK_HORIZON) return WORKTIME; + if (off > NOW_HORIZON) return NEARTIME; + if (off > Time::ZERO) return SPINTIME; + else return DISPATCH; } - microseconds - scatteredDelayTime() + Time + scatteredDelayTime (Capacity capacity) { UNIMPLEMENTED ("establish a randomised targeted delay time"); } diff --git a/src/vault/gear/scheduler.hpp b/src/vault/gear/scheduler.hpp index 490103d6d..a306d95a6 100644 --- a/src/vault/gear/scheduler.hpp +++ b/src/vault/gear/scheduler.hpp @@ -64,6 +64,10 @@ namespace gear { // using util::isnil; // using std::string; using std::move; + using lib::time::Time; + using lib::time::FSecs; + using lib::time::Offset; + using lib::time::Duration; namespace { // Scheduler default config diff --git a/tests/vault/gear/scheduler-load-control-test.cpp b/tests/vault/gear/scheduler-load-control-test.cpp index 003355b12..4d4a14abc 100644 --- a/tests/vault/gear/scheduler-load-control-test.cpp +++ b/tests/vault/gear/scheduler-load-control-test.cpp @@ -45,6 +45,7 @@ namespace test { // using lib::time::FrameRate; // using lib::time::Offset; // using lib::time::Time; + using Capacity = LoadController::Capacity; @@ -63,19 +64,59 @@ namespace test { run (Arg) { simpleUsage(); + classifyTimings(); walkingDeadline(); setupLalup(); } /** @test TODO demonstrate a simple usage scenario - * @todo WIP 10/23 ✔ define ⟶ 🔁 implement + * @todo WIP 10/23 🔁 define ⟶ 🔁 implement */ void simpleUsage() { BlockFlowAlloc bFlow; - LoadController lcontrl{bFlow}; + LoadController ctrl{bFlow}; + } + + + + /** @test verify classification of time horizon for scheduling. + * - if the next planned Activity lies beyond the SLEEP_HORIZON, + * then the current thread can be considered part of the _idle capacity_ + * - in a similar way, WORK_HORIZON delineates the zone of repeated incoming + * Activities from the zone considered part of current active operation + * - Activities within the NOW_HORIZON can be awaited by yield-spinning + * - and any event from current into the past will be scheduled right away + * @todo WIP 10/23 ✔ define ⟶ ✔ implement + */ + void + classifyTimings() + { + BlockFlowAlloc bFlow; + LoadController ctrl{bFlow}; + + Time next{0,10}; + + Time ut{1,0}; + Time t1{0,9}; + Time t2{next - SLEEP_HORIZON}; + Time t21{t2 + ut}; + Time t3{next - WORK_HORIZON}; + Time t31{t3 + ut}; + Time t4{next - NOW_HORIZON}; + + CHECK (Capacity::IDLETIME == LoadController::classifyCapacity (Offset{next - ut })); + CHECK (Capacity::IDLETIME == LoadController::classifyCapacity (Offset{next - t1 })); + CHECK (Capacity::WORKTIME == LoadController::classifyCapacity (Offset{next - t2 })); + CHECK (Capacity::WORKTIME == LoadController::classifyCapacity (Offset{next - t21})); + CHECK (Capacity::NEARTIME == LoadController::classifyCapacity (Offset{next - t3 })); + CHECK (Capacity::NEARTIME == LoadController::classifyCapacity (Offset{next - t31})); + CHECK (Capacity::SPINTIME == LoadController::classifyCapacity (Offset{next - t4 })); + + CHECK (Capacity::DISPATCH == LoadController::classifyCapacity (Offset::ZERO )); + CHECK (Capacity::DISPATCH == LoadController::classifyCapacity (Offset{t4 - next })); } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 2e4cd5341..c2f57bc88 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -81970,6 +81970,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + @@ -81991,6 +81994,11 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + @@ -82436,6 +82444,30 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + + + + + + + + +

+ ....denn dann kommen die periodischen Worker sofort zum Zug +

+ +
+
+
+
@@ -82483,13 +82515,95 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - - - - - + + + + + + + + + + +

+ sent to work +

+ + +
+
+ + + + + + +

+ reserved for next task +

+ + +
+
+ + + + + + +

+ awaiting imminent activities +

+ + +
+
+ + + + + + +

+ capacity for active processing required +

+ + +
+
+ + + + + + +

+ typical stable work task rhythm expected +

+ + +
+
+ + + + + + +

+ time to go to sleep +

+ + +
+
+
+ + + + + + @@ -82517,6 +82631,10 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + +