diff --git a/src/vault/gear/activity-lang.cpp b/src/vault/gear/activity-lang.cpp index 19f2723c6..7a0557406 100644 --- a/src/vault/gear/activity-lang.cpp +++ b/src/vault/gear/activity-lang.cpp @@ -21,35 +21,29 @@ * *****************************************************/ /** @file activity-lang.cpp - ** Implementation details of the scheduler activity language framework. - ** - ** @todo WIP-WIP-WIP 8/2023 »Playback Vertical Slice« - ** + ** Supporting implementation for the scheduler activity language framework. + ** @note most of the language processing is defined as inline functions + ** and uses fixed-size data storage in a dedicated custom allocator. + ** Timing measurements confirmed the benefits, reducing invocations + ** from ~50µs to <5µs in optimised mode, and this indeed matters, + ** as the scheduler can be considered performance sensitive code. */ #include "vault/gear/activity-lang.hpp" -//#include "lib/symbol.hpp" -//#include "include/logging.h" #include "lib/format-obj.hpp" #include using std::string; -//using util::isnil; using lib::time::Time; using lib::time::TimeValue; namespace vault{ namespace gear { - - namespace { // internal details - - } // internal details - namespace activity { - + Hook::~Hook() { } // emit VTable here... } @@ -130,9 +124,4 @@ namespace gear { } - - - /** - */ - }} // namespace vault::gear diff --git a/src/vault/gear/load-controller.hpp b/src/vault/gear/load-controller.hpp index 8f463b137..e34238c74 100644 --- a/src/vault/gear/load-controller.hpp +++ b/src/vault/gear/load-controller.hpp @@ -158,6 +158,13 @@ namespace gear { /////////////////////////////////////////////////////////////////////////////OOO build integrated load state } + /** statistics update on scaling down the WorkForce */ + void + markWorkerExit() + { + ///////do something deeply moving + } + /** * did we already tend for the indicated next relevant head time? * @note const and non-grooming diff --git a/src/vault/gear/scheduler.cpp b/src/vault/gear/scheduler.cpp deleted file mode 100644 index 66d8cf486..000000000 --- a/src/vault/gear/scheduler.cpp +++ /dev/null @@ -1,68 +0,0 @@ -/* - Scheduler - coordination of render activities under timing and dependency constraints - - Copyright (C) Lumiera.org - 2023, Hermann Vosseler - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -* *****************************************************/ - -/** @file scheduler.cpp - ** Implementation of messaging, dispatch and prioritisation of render activities. - ** The _scheduling_ functionality is assembled from low-level operational building blocks. - ** - ** @note as of X/2023 this is complete bs - ** @todo WIP ///////////////////////TICKET # - ** - ** @see ////TODO_test usage example - ** @see scheduler.cpp implementation - ** - ** @todo WIP-WIP-WIP 6/2023 »Playback Vertical Slice« - ** - */ - - -#include "vault/gear/scheduler.hpp" -//#include "lib/symbol.hpp" -//#include "include/logging.h" - -//#include - -//using std::string; -//using util::isnil; - - -namespace vault{ -namespace gear { - - namespace { // internal details - - } // internal details - - namespace activity { - - Hook::~Hook() { } // emit VTable here... - } - -// NA::~NA() { } - - - - - /** - */ - -}} // namespace vault::gear diff --git a/src/vault/gear/scheduler.hpp b/src/vault/gear/scheduler.hpp index 83a4434f4..c6196bb05 100644 --- a/src/vault/gear/scheduler.hpp +++ b/src/vault/gear/scheduler.hpp @@ -186,22 +186,28 @@ namespace gear { } /** - * Spark the engine self-regulation cycle and power up WorkForce + * Spark the engine self-regulation cycle and power up WorkForce. + * @note set off automatically when [put to use](\ref #seedCalcStream); + * while active, the [duty-cycle](\ref #handleDutyCycle) retains + * itself, albeit bound to disengage when falling empty. */ void ignite() { TRACE (engine, "Ignite Scheduler Dispatch."); handleDutyCycle (RealClock::now()); - workForce_.activate(); + if (not empty()) + workForce_.activate(); } /** * Bring down processing destructively as fast as possible. * Dismiss worker threads as soon as possible, and clear the queues. - * @warning Currently running Activities can not be aborted, but anything - * not yet scheduled will be discarded, irrespective of dependencies + * @warning Actually running Activities can not be aborted, but anything + * not yet scheduled will be discarded, irrespective of dependencies. + * @remark should never need to call this in regular operation, + * since an empty scheduler disengages automatically. */ void terminateProcessing() @@ -251,12 +257,7 @@ namespace gear { private: void handleDutyCycle (Time now); - - void - handleWorkerTermination (bool isFailure) - { - UNIMPLEMENTED("die harder"); - } + void handleWorkerTermination (bool isFailure); void triggerEmergency(); @@ -529,6 +530,19 @@ namespace gear { } } + /** + * Callback invoked whenever a worker-thread is about to exit + * @param isFailuere if the exit was caused by uncaught exception + */ + inline void + Scheduler::handleWorkerTermination (bool isFailure) + { + if (isFailure) + triggerEmergency(); + else + loadControl_.markWorkerExit(); + } + /** * Trip the emergency brake and unwind processing while retaining all state. */ diff --git a/tests/vault/gear/scheduler-service-test.cpp b/tests/vault/gear/scheduler-service-test.cpp index 20a48d821..92cd0b86b 100644 --- a/tests/vault/gear/scheduler-service-test.cpp +++ b/tests/vault/gear/scheduler-service-test.cpp @@ -77,6 +77,7 @@ namespace test { { simpleUsage(); verify_StartStop(); + verify_Disengage(); invokeWorkFunction(); walkingDeadline(); } @@ -95,8 +96,8 @@ namespace test { - /** @test TODO get the scheduler into running state - * @todo WIP 10/23 ✔ define ⟶ 🔁 implement + /** @test get the scheduler into running state + * @todo WIP 10/23 ✔ define ⟶ ✔ implement */ void verify_StartStop() @@ -105,12 +106,61 @@ namespace test { EngineObserver watch; Scheduler scheduler{bFlow, watch}; CHECK (isnil (scheduler)); + + Activity dummy{uint64_t(123), uint64_t(456)}; + auto postIt = [&] { auto& schedCtx = Scheduler::ExecutionCtx::from(scheduler); + schedCtx.post (RealClock::now()+t200us, &dummy, schedCtx); + }; + scheduler.ignite(); + CHECK (isnil (scheduler)); // no start without any post() + + postIt(); scheduler.ignite(); CHECK (not isnil (scheduler)); scheduler.terminateProcessing(); CHECK (isnil (scheduler)); + + postIt(); + postIt(); + scheduler.ignite(); + CHECK (not isnil (scheduler)); + //... and just walk away => scheduler unwinds cleanly from destructor + }// Note: BlockFlow and WorkForce unwinding is covered in dedicated tests + + + + /** @test TODO verify the scheduler processes and winds down automatically + * when falling empty. + * @todo WIP 10/23 ✔ define ⟶ 🔁 implement + */ + void + verify_Disengage() + { + BlockFlowAlloc bFlow; + EngineObserver watch; + Scheduler scheduler{bFlow, watch}; + CHECK (isnil (scheduler)); + + Activity dummy{uint64_t(123), uint64_t(456)}; + auto postIt = [&] { auto& schedCtx = Scheduler::ExecutionCtx::from(scheduler); + schedCtx.post (RealClock::now()+t200us, &dummy, schedCtx); + }; + + UNIMPLEMENTED("disengage"); + scheduler.ignite(); + CHECK (isnil (scheduler)); + + postIt(); + scheduler.ignite(); + CHECK (not isnil (scheduler)); +SHOW_EXPR(_raw(RealClock::now())) +SHOW_EXPR(_raw(scheduler.layer1_.headTime())) + + scheduler.terminateProcessing(); + CHECK (isnil (scheduler)); +SHOW_EXPR(_raw(RealClock::now())) } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index eef1e7323..83461c159 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -80928,7 +80928,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200

- + @@ -82380,7 +82380,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -82394,8 +82394,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + @@ -82431,6 +82431,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + +
@@ -82681,12 +82684,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - - + + + @@ -82700,15 +82704,28 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + + + + +

+ bedeutet ⟹ ein momentan nicht benötigter Worker darf duchaus mal 200µs schlafen +

+ +
+
- - - + + + + + + @@ -82716,14 +82733,16 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + - + + @@ -82758,10 +82777,12 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + + + - + @@ -82769,6 +82790,20 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + +

+ wenn sich der Tick nicht mehr erneuert, +

+

+ ist der Scheduler stehen geblieben +

+ +
+ +
@@ -82850,6 +82885,10 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + +
@@ -83330,17 +83369,19 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - - - - - - - + + + + + + + + + @@ -83354,8 +83395,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + @@ -83404,6 +83445,26 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + +

+ stelle fest: das wirkt (unbeabsichtigterweise) auch als Zünd-Barriere +

+ +
+ +
+ + + + +
@@ -83422,6 +83483,10 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + +
@@ -83440,10 +83505,10 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - - + + + + @@ -83459,23 +83524,24 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + - + + - - - - + + + + - + @@ -83483,8 +83549,33 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + + + + + + +
    +
  • + hab mir den jeweiligen Quellcode angeschaut und keine Probleme gesehen +
  • +
  • + auch auf Stackoverflow gibt es Hinweise, daß das so gehen soll (obwohl es kein richtiges API dafür gibt) +
  • +
  • + Timing-Messungen zeigen: braucht 100µs +
  • +
  • + danach kommt die gleiche Scheduler-Instanz wieder auf die Beine und verhält sich unauffällig. +
  • +
  • + auch ist der erneute Start nicht feststellbar langsamer; das Hochfahren der WorkForce braucht ohnehin 1ms +
  • +
+ +
+
@@ -83507,7 +83598,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -83519,8 +83610,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- +
@@ -87487,7 +87578,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -89006,10 +89097,182 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - - + + + + + + + + + +

+ ohne das wird nämlich gar kein Tick-Zyklus aufgemacht +

+ +
+ + + +

+ ...da ich das handleDutyCycle eben auch zum „zünden“ verwenden möchte, entsteht hier so etwas wie eine zusätzliche Zünd-Barriere; war nicht geplant, gefällt mir aber: man muß also auch Holz in den Ofen stecken, bevor man zündet... +

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

+ Post+erfolgreiche Zündung: 1ms +

+ +
+
+ + + + +

+ gewaltsam Angehalten: 60ms +

+ +
+ + + + +

+ korrekt! +

+
    +
  • + tend-next auf den Tick ≔ 50ms +
  • +
  • + WorkForce wartet beim Shutdown pauschal 20ms (IDLE_WAIT) +
  • +
+ +
+ + + +
+
+ + + + + + +

+ nochmal zünden: 1ms +

+ +
+
+ + + + +

+ Cache-Effekte können mehrere 100µs ausmchen. Dennoch ist dieses Pattern mit leichten Schwankungen reproduzierbar in vielen Läufen, und zeigt auch fast keinen Unterschied, wenn mit -O3 gebaut. Man beachte im Besonderen daß bei der ersten Fehlzündung die WorkForce nicht hochskaliert wird. Da steckt also die Zeit, und das ist auch mit dem WorkForce_test konsistent. +

+ +
+ +
+
+ + + + +

+ Fazit: Verhalten wie erwartet +

+ +
+ + + + + +
    +
  • + der erste gestartete Thread macht alle Dispaches in Serie +
  • +
  • + danach wird er per tendNext gezielt auf den Tick +50ms geschickt +
  • +
  • + alle anderen Threads gehen in höchstens einen Wartezyklus +
  • +
  • + der WorkForce-Destruktor wartet jeweils auf den tendNext-Thread +
  • +
+ +
+ + + +

+ Im Detail nachvollzogen durch Trace-Log der aktuellen Zeit, incl. Thread-Nummer +

+
    +
  • + man konnte jeweils deutliche Cache-Effekte beobachten +
  • +
  • + abgesehen davon waren die Timings stabil +
  • +
  • + die separat im Test gemessenen Zeiten passen perfekt mit den Zeiten und dem Muster aus dem Trace zusammen +
  • +
  • + die Threads brauchen in der Tat eine Zeitspanne von 100-200µs, bis sie aktiv werden +
  • +
  • + insofern variiert es von Lauf zu Lauf erheblich, wie viele Threads überhaupt den Schlaf-Auftrag bekommen +
  • +
+ +
+ +
+
+
+ + + + + + + + + + + @@ -91746,6 +92009,20 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + + + + + +
@@ -92463,16 +92740,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

Für die Implementierung des Schedulers sind Deadlines technisch irrelevant — deshalb habe ich aktuell auch die ganze Implementierung gecodet, und noch keine Deadline-Behandlung eingebaut. Aber sowohl logisch, als auch im Hinblick auf die Allokationen muß jede Activity eine Deadline haben. Eine Activity ohne Deadline wäre ultimativ verbindlich: irgendwann nach ihrem Startzeitpunkt muß sie aktiviert werden, und jede spätere Activity ist durch sie verdeckt

- -
+
@@ -92485,9 +92759,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

Problem: wenn Rückstau im Scheduler entsteht, @@ -92496,8 +92768,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
wird jede Deadline irgendwann überfahren

- -
+ @@ -92508,55 +92779,43 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

...es gibt zwar einen Block-Mechanismus (das Epoch-Gate), doch damit läßt man den Block-Pool anwachsen und die lineare Suche durch die Blöcke macht sich bald bemerkbar

- -
+
- - - +

...weil dann der Überlauf-Mechanismus zu weit in die Zukunft ausgreift, und sich die Steuerung in absehbarer Zeit nicht mehr fängt.

- -
+
- - - +

Daher ist es nicht möglich, eben sehr große/lange Tasks vorzuplanen; man erzeugt eine Flut leerer Blöcke dazwischen, und die Suchzeiten degenerieren

- -
+
- - - +

Damit scheiden die naheliegenden Reparatur-Ansätze weitgehend aus: es ist nicht möglich, eine „überfahrene“ Activity in die Zukunft zu kopieren, denn wenn die Deadline verstrichen ist, kann sie auch bereits verworfen worden sein, und es ist nicht mehr feststellbar, um was es sich handelte

- -
+
@@ -92580,16 +92839,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

der Tick selber ist eine verbindliche Aktivität

- -
+
@@ -92617,31 +92873,25 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

sollte automatisch passieren, wenn die internen Gesundheits-Checks scheitern

- -
+
- - - +

...wenn wir noch einen Worker-Pool haben, der mit langlaufenden berechnungen geblockt ist, dann wird jeder reguläre emergency-Shutdown im Destruktor hängen bleiben.Wenn es erst mal so weit gekommen ist, kann man nicht einmal mehr den User richtig benachrichtigen; ein hartes exit()  dagegen würde die bereits geleistete Arbeit einfach verwerfen.

- -
+
@@ -92909,9 +93159,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

Der Scheduler arbeitet nur in einem begrenzten Bereich @@ -92923,8 +93171,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
Dann ist ein spezielle Notzustand zu aktivieren

- -
+
@@ -92943,16 +93190,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

...der gesamte Entwurf bisher beruht auf Ereignis-Verknüpfung und sieht keine generische Erfassung aller tatsächlich berechneten Schritte vor. Wenn also die Scheduler-Emergency ausgelöst wird, ist ein zufällig verteiltes Muster an Einzel-Jobs abgeschlossen, und der Rest fehlt. Wollte man hier einen generischen Ansatz für das Wiederanlaufen schaffen, so müßte man alle Berechnungsprozesse unter eine einzige Abstraktion subsummieren können. Das halte ich für gefährlich...

- -
+
@@ -93067,16 +93311,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

Beispiel: ein »final Render« bei dem pro Frame mehr als 30 Sekunden Rechenzeit notwendig sind; dies würde die Auslegung des Schedulers sprengen

- -
+