diff --git a/src/vault/gear/load-controller.hpp b/src/vault/gear/load-controller.hpp index 329220c91..13e84792b 100644 --- a/src/vault/gear/load-controller.hpp +++ b/src/vault/gear/load-controller.hpp @@ -129,9 +129,10 @@ namespace gear { } - Duration SLEEP_HORIZON{_uTicks (20ms)}; - Duration WORK_HORIZON {_uTicks ( 5ms)}; - Duration NEAR_HORIZON {_uTicks (50us)}; + Duration SLEEP_HORIZON{_uTicks (20ms)}; ///< schedules beyond that horizon justify going idle + Duration WORK_HORIZON {_uTicks ( 5ms)}; ///< the scope of activity _currently in the works_ + Duration NEAR_HORIZON {_uTicks (50us)}; ///< what counts as "imminent" (e.g. for spin-waiting) + Duration STANDARD_LAG {_uTicks(200us)}; ///< Experience shows that on average scheduling happens with 200µs delay const double LAG_SAMPLE_DAMPING = 2; ///< smoothing factor for exponential moving average of lag; } @@ -154,6 +155,7 @@ namespace gear { { function maxCapacity {[]{ return 1; }}; function currWorkForceSize{[]{ return 0; }}; + function stepUpWorkForce{[](uint){/*NOP*/}}; ///////TODO add here functors to access performance indicators }; @@ -232,8 +234,8 @@ namespace gear { double effectiveLoad() const { - double lag = sampledLag_.load (memory_order_relaxed); - lag -= 200; + double lag = averageLag(); + lag -= _raw(STANDARD_LAG); lag /= _raw(WORK_HORIZON); lag *= 10; double lagFactor = lag<0? 1/(1-lag): 1+lag; @@ -246,6 +248,13 @@ namespace gear { updateState (Time) { /////////////////////////////////////////////////////////////////////////////TODO anything we need to calculate on each »scheduler tick«? + // + auto lag = averageLag(); + if (lag > _raw(WORK_HORIZON)) + wiring_.stepUpWorkForce(+4); + else + if (averageLag() > 2*_raw(STANDARD_LAG)) + wiring_.stepUpWorkForce(+1); } /** statistics update on scaling down the WorkForce */ @@ -255,6 +264,18 @@ namespace gear { ///////do something deeply moving } + /** + * Hook to check and possibly scale up WorkForce to handle one additional job + */ + void + ensureCapacity (Time startHorizon) + { + if (startHorizon > 2* SLEEP_HORIZON) + return; + if (averageLag() > 2*_raw(STANDARD_LAG)) + wiring_.stepUpWorkForce(+1); + } + /** * did we already tend for the indicated next relevant head time? * @note const and non-grooming diff --git a/src/vault/gear/scheduler.hpp b/src/vault/gear/scheduler.hpp index cc563d440..3fd71806d 100644 --- a/src/vault/gear/scheduler.hpp +++ b/src/vault/gear/scheduler.hpp @@ -142,6 +142,7 @@ namespace gear { const auto IDLE_WAIT = 20ms; ///< sleep-recheck cycle for workers deemed _idle_ const size_t DISMISS_CYCLES = 100; ///< number of wait cycles before an idle worker terminates completely Offset POLL_WAIT_DELAY{FSecs(1,1000)}; ///< delay until re-evaluating a condition previously found unsatisfied + Offset SEED_CALC_OFFSET{_uTicks(250us)}; ///< tiny delay to ensure the first job is actually enqueued to force ignite() Offset DUTY_CYCLE_PERIOD{FSecs(1,20)}; ///< period of the regular scheduler »tick« for state maintenance. Offset DUTY_CYCLE_TOLERANCE{FSecs(1,10)}; ///< maximum slip tolerated on duty-cycle start before triggering Scheduler-emergency } @@ -271,7 +272,8 @@ namespace gear { ignite() { TRACE (engine, "Ignite Scheduler Dispatch."); - handleDutyCycle (RealClock::now()); + bool force_continued_run{true}; + handleDutyCycle (RealClock::now(), force_continued_run); if (not empty()) workForce_.activate(); } @@ -330,7 +332,7 @@ namespace gear { { layer1_.activate (manID); activityLang_.announceLoad (expectedAdditionalLoad); - continueMetaJob (RealClock::now(), planningJob, manID); + continueMetaJob (RealClock::now()+SEED_CALC_OFFSET, planningJob, manID); } @@ -379,9 +381,9 @@ namespace gear { private: void postChain (ActivationEvent); - void handleDutyCycle (Time now); + void handleDutyCycle (Time now, bool =false); void handleWorkerTermination (bool isFailure); - void maybeScaleWorkForce(); + void maybeScaleWorkForce (Time startHorizon); void triggerEmergency(); @@ -425,6 +427,7 @@ namespace gear { LoadController::Wiring setup; setup.maxCapacity = []{ return work::Config::COMPUTATION_CAPACITY; }; setup.currWorkForceSize = [this]{ return workForce_.size(); }; + setup.stepUpWorkForce = [this](uint steps){ workForce_.incScale(steps); }; return setup; } @@ -497,7 +500,7 @@ namespace gear { /** * λ-post: enqueue for time-bound execution, possibly dispatch immediately. - * @remark This function represents and _abstracted entrance to scheduling_ + * @remark This function represents an _abstracted entrance to scheduling_ * for the ActivityLang and is relevant for recursive forwarding * of activations and notifications. The concrete implementation * needs some further contextual information, which is passed @@ -614,7 +617,7 @@ namespace{ inline void Scheduler::postChain (ActivationEvent actEvent) { - maybeScaleWorkForce (); + maybeScaleWorkForce (actEvent.startTime()); cout<<"‖SCH‖ "+markThread()+": @"+relT(RealClock::now())+" ○ start="+relT(actEvent.starting)+" dead:"+util::toString(actEvent.deadline - actEvent.starting)< 2sec). */ inline void - Scheduler::maybeScaleWorkForce() + Scheduler::maybeScaleWorkForce (Time startHorizon) { if (empty()) { @@ -807,7 +814,7 @@ cout<<"‖IGN‖ wof:"+util::toString(workForce_.size())<= setup_.COMPUTATION_CAPACITY) - return; - else + uint i = workers_.size(); + uint target = util::min (i+step, setup_.COMPUTATION_CAPACITY); + for ( ; i < target; ++i) workers_.emplace_back (setup_); } diff --git a/tests/vault/gear/scheduler-service-test.cpp b/tests/vault/gear/scheduler-service-test.cpp index b39dd3001..5b5d6e8d9 100644 --- a/tests/vault/gear/scheduler-service-test.cpp +++ b/tests/vault/gear/scheduler-service-test.cpp @@ -134,10 +134,9 @@ namespace test { auto postIt = [&] { postNewTask (scheduler, dummy, RealClock::now()+t200us); }; scheduler.ignite(); - CHECK (isnil (scheduler)); // no start without any post() + CHECK (not isnil (scheduler));// repeated »tick« task enlisted.... postIt(); - scheduler.ignite(); CHECK (not isnil (scheduler)); scheduler.terminateProcessing(); diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 1f5e346a3..b51fba85d 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -81251,6 +81251,10 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + @@ -82626,18 +82630,14 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + - - - - @@ -82752,7 +82752,56 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + + + + + + + + + + + +

+ ...ich hab nämlich nur an mich selber gedacht, wie ich mit dem Debugger noch in den ersten Planungs-Chunk hineinsteppen kann. Das ist doch cool, dachte ich. Diese Perspektive ist aber schräg — wenngleich es sicher gut ist, daß aus Konsistenzgründen sogar der aktuelle Thread ein Stück Arbeit machen könnte, so ist das letztlich doch nicht die Aufrgabe des aktuellen Thread (Player?), und es kostet (wie ich grade feststelle) gerne mal 5-10ms, die woanders besser angelegt sind. Zumal die Festlegung des »start-Ankers« logisch davon unabhängig ist, von wo aus das läuft; und wenn man den Anker explizit auf etwas setzen möchte, dann soll man ihn eben explizit setzen. +

+ +
+
+ + + + +

+ Genau so einen Fall beobachte ich grade: +

+
    +
  • + es gibt einen weiteren Bug, weshalb der Meta-Job zunächst einmal leer + durchfällt +
  • +
  • + demzufolge wird außer dem aktuellen Job nichts „abgeworfen“ +
  • +
  • + und dann fährt der Scheduler überhaupt nicht hoch +
  • +
  • + und eine Logik, die letzteres vorraussetzt, läuft dann in den Deadlock + �� +
  • +
+ +
+ +
+
+ + + +
@@ -84818,7 +84867,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -84914,6 +84963,10 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + +
@@ -84931,22 +84984,76 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - - +

- daher muß für einen neuen Job ein step up erfolgen + Scheduler ⟶ laufend und genügend Kapazität bereitgestellt

- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ warum....? weiß nicht, Bauchgefühl. +

+

+ Ich möchte nicht mit Einzelfall-Analysen belegen, wann der Scheduler gestartet werden soll. Der Ruhe-Zustand, wie auch der Neuanlauf sollten von außen praktisch nicht erkennbar sein (bis auf die Verzögerung) +

+ + +
+
+ + + +
+ + + + + + + + + + + + + @@ -84962,7 +85069,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -85011,6 +85118,51 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -95697,8 +95849,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + @@ -95731,6 +95884,49 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -100616,7 +100812,31 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + +

+ Promise muß wirklich vor dem Start-Trigger vorbereitet werden +

+ +
+ + + + + +

+ ...ich erinnere mich: man muß mit 0-Timeout darauf warten und das bedingt mindestens einen yield-wait +

+ +
+ +
+
+
@@ -101041,6 +101261,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + +
@@ -101128,6 +101351,152 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ Nebeneinsicht: darf nur ein wirklich leeres Signal kaputt schießen +

+ + +
+ +
+
+ + + +
+ + + + + + + +

+ Hilfsfunktion, die beim ersten Aufruf den Bezugspunkt setzt. Dafür sorgen, daß dieser erste Aufruf den Anker-Punkt anfragt. Hilfsfunktion gibt dann µs nach Anker aus +

+ + +
+ +
+ + + + + + + + + + + +

+ ⟹ der pre-Roll ist wesentlich zu kurz +

+ + +
+ +
+
+ + + + + + + + + + + + + + + + +

+ Graph als DOT anschauen, ist ja reproduzierbar und enthält die Node-IDs +

+ + +
+ +
+
+ + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + +
@@ -102270,8 +102639,72 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ Wie viel dedizierte Logik ist hierfür sinnvoll? +

+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + @@ -108285,6 +108718,97 @@ class Something + + + + + + + + + + + +

+ es ist ein einmal-Kommunikationskanal +

+ + +
+
+ + + + + + + + + + + + + +

+ Endpunkte werden normalerweise vorher erstellt +

+ + +
+ + + + + +

+ ...will sagen, bevor die »concurrent operation« überhaupt beginnt; also beispielsweise bevor man einen Worker-Thread startet, ist zumindest der Promise (und damit der shared-state) schon erzeugt. Ein Future kann man dann später davon ableiten (is clearly sequenced). Wenn man Endpunkte über Thread-Grenzen hinweg weitereichen möchte, ist das nicht durch den Future-Promise-Mechanismus gedeckt und muß anderweitig konventionell synchronisiert werden. +

+ + +
+
+
+
+ + + + + + + + + + + + + + + + +

+ also ein shared_ptr != null +

+ + +
+
+ + + + + + + + + + + + + + + + +