From 26b2e6f1bd883457263ffd5045827657ca80dcfc Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 20 Oct 2023 18:24:50 +0200 Subject: [PATCH] Scheduler: solve the initialisation of WorkForce Notably I wanted an entirely static and direct binding to the internals of the Scheduler, which can be completely inlined. The chosen solution also has the benefit of making the back-reference to the Scheduler explicitly visible to the reader. This is relevant, since the Config-Subobject is *copied* into each Worker instance. --- src/vault/gear/scheduler.hpp | 35 ++++++++++---- src/vault/gear/work-force.hpp | 5 +- wiki/thinkPad.ichthyo.mm | 90 +++++++++++++++++++++++++++++++---- 3 files changed, 108 insertions(+), 22 deletions(-) diff --git a/src/vault/gear/scheduler.hpp b/src/vault/gear/scheduler.hpp index 4d635d9f5..ebd80adf0 100644 --- a/src/vault/gear/scheduler.hpp +++ b/src/vault/gear/scheduler.hpp @@ -63,9 +63,17 @@ namespace gear { // using util::isnil; // using std::string; + namespace { // Scheduler default config + + const auto IDLE_WAIT = 20ms; + const size_t DISMISS_CYCLES = 100; + } - /** - * Schedule and coordinate render activities. + + + + /******************************************************//** + * »Scheduler-Service« : coordinate render activities. * @todo WIP-WIP 6/2023 * @see BlockFlow * @see SchedulerUsage_test @@ -73,23 +81,30 @@ namespace gear { class Scheduler : util::NonCopyable { - using Setup = work::Config; ////////////////////////////////////////////////////OOO actually need subclass to attach the work-function + /** Binding of worker callbacks to the scheduler implementation */ + struct Setup : work::Config + { + Scheduler& scheduler; + activity::Proc doWork() { return scheduler.getWork(); } + void finalHook (bool _) { scheduler.handleWorkerTermination(_);} + }; + SchedulerInvocation layer1_; SchedulerCommutator layer2_; -// WorkForce workForce_; + WorkForce workForce_; ActivityLang activityLang_; LoadController loadControl_; EngineObserver& engineObserver_; + public: - explicit Scheduler (BlockFlowAlloc& activityAllocator ,EngineObserver& engineObserver) : layer1_{} , layer2_{} -// , workForce_{connectWorkers()} + , workForce_{Setup{IDLE_WAIT, DISMISS_CYCLES, *this}} , activityLang_{activityAllocator} , loadControl_{activityAllocator} , engineObserver_{engineObserver} @@ -139,17 +154,17 @@ namespace gear { /** * */ - void + activity::Proc getWork() { UNIMPLEMENTED("the Worker-Funkction"); } private: - Setup - connectWorkers() + void + handleWorkerTermination (bool isFailure) { - UNIMPLEMENTED("build Worker pool operational setup"); + UNIMPLEMENTED("die harder"); } }; diff --git a/src/vault/gear/work-force.hpp b/src/vault/gear/work-force.hpp index a0246c362..4c9fc195c 100644 --- a/src/vault/gear/work-force.hpp +++ b/src/vault/gear/work-force.hpp @@ -72,12 +72,12 @@ namespace gear { using std::move; using std::atomic; using util::unConst; + using std::chrono::milliseconds; + using std::chrono_literals::operator ""ms; using std::this_thread::sleep_for; namespace work { ///< Details of WorkForce (worker pool) implementation - using std::chrono::milliseconds; - using std::chrono_literals::operator ""ms; using SIG_WorkFun = activity::Proc(void); ///< config should define a callable with this signature to perform work using SIG_FinalHook = void(bool); ///< config should define callable invoked at exit (argument: isFailure) @@ -94,7 +94,6 @@ namespace gear { struct Config { static const size_t COMPUTATION_CAPACITY; - const size_t EXPECTED_MAX_POOL = 1.5*COMPUTATION_CAPACITY; const milliseconds IDLE_WAIT = 20ms; const size_t DISMISS_CYCLES = 100; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 258d26988..d85580a0b 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -81765,15 +81765,44 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - - - + + + + + +

+ Schwere Geburt... +

+

+ Irgendwie hatte sich bei mir die Idee mit den Lambdas so festgefressen — obwohl ich doch schon eingesehen hatte, daß C++ in der Hinsicht (aus gutem Grund) sehr konsequent ist, und keine Hintertür bietet, um Laufzeit-Bindigns in die statische Ebene „zu schmuggeln“. Was mich dann aber auch extrem gestört hat, waren die std::function-Felder, in die man die Lambdas speichern müßte. Es wäre nämlich eine andere Lösung denkbar, bei der diese Function-Objekte schon in der Basis-Config für die WorkForce enthalten sind; damit wäre das Design auch komplett festgelgt (und der Test würde sogar einfacher). Trotzdem habe ich eine Abneigung gegen diese Function-Objekte, weil sie eben komplett unnötig sind, da es sich effektiv um ein statisches Binding handeln sollte. Und das war dann das rettende Stichwort: dann definiert man eben diese Binding-Forwarder als statische Funktionen in einer nested Class, und macht die Rückreferenz auf den Scheduler explizit. Und mit Aggregat-Initialisierung kann man dann sogar noch die Definition des Konstruktors auslassen +

+ +
+ + + + + +
+ + + + + + + + + + + + +
@@ -81799,6 +81828,19 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + + + + +
@@ -84509,7 +84551,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -84520,10 +84562,21 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + + + + + +

+ Denn jeder Aufruf des Fork-Funktors wird unterschiedlich lang dauern. Und selbst wenn alle Worker idle sind: dann gibt es bei der ersten Runde die Mega-Contention, und danach liegen die Wartezyklen aller Worker leicht gestaffelt. Contention wäre nur dann ein Problem, wenn jemand, der arbeiten könnte, vom Arbeiten abgehalten wird. +

+ +
+
+
@@ -84666,7 +84719,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -84675,6 +84728,23 @@ Date:   Thu Apr 20 18:53:17 2023 +0200

+ + + + +

+ ...wie so oft, vor allem getrieben durch die Tests: die Layer wurden zu einer Sammlung von Implementierungs-Bausteienen, welche auch aufeinander aufbauen. Aber die große Verdrahtung habe ich noch vor mir hergeschoben — sogar so weit, daß Layer-2 selbst keine Referenz auf Layer-1 besitzt (sondern diese für jede Funktion hereingereicht bekommt). Damit wird die Verdrahtung nun sternförmig, und der Binde-Code ist das, was den »Scheduler-Service« ausmacht +

+ +
+ +
+ + + + + +
@@ -84741,6 +84811,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ @@ -84819,10 +84890,11 @@ Date:   Thu Apr 20 18:53:17 2023 +0200

- C++ zwingt uns dazu, explizit das zu tun was ohnehin getan werden muß; da jedoch der Typ der Config per Template-Parameter gewählt wird, ist komplettes Inlining möglich; letztlich wird daher nur ein Pointer auf das Scheduler-Objekt in alle Threads kopiert — exakt das  was wir brauchen + C++ zwingt uns dazu, explizit das zu tun was ohnehin getan werden muß; da jedoch der Typ der Config per Template-Parameter gewählt wird, ist komplettes Inlining möglich; letztlich wird daher nur ein Pointer auf das Scheduler-Objekt in alle Threads kopiert — exakt das was wir brauchen

+