From 707fbc29334f6972875e0f3eab4be2bbd4eab6c5 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Wed, 20 Dec 2023 20:19:10 +0100 Subject: [PATCH] Scheduler-test: implement contention mitigation scheme MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit while my basic assessment is still that contention will not play a significant role given the expected real world usage scenario — when testing with tighter schedule and rather short jobs (500µs), some phases of massive contention can be observed, leading to significant slow-down of the test. The major problem seems to be that extended phases of contention will effectively cause several workers to remain in an active spinning-loop for multiple microseconds, while also permanently reading the atomic lock. Thus an adaptive scheme is introduced: after some repeated contention events, workers now throttle down by themselves, with polling delays increased with exponential stepping up to 2ms. This turns out to be surprisingly effective and completely removes any observed delays in the test setup. --- src/vault/gear/activity.hpp | 3 +- src/vault/gear/scheduler-commutator.hpp | 4 +- src/vault/gear/scheduler.hpp | 1 + src/vault/gear/work-force.cpp | 33 + src/vault/gear/work-force.hpp | 65 +- .../vault/gear/scheduler-commutator-test.cpp | 4 +- tests/vault/gear/work-force-test.cpp | 26 + wiki/renderengine.html | 3 +- wiki/thinkPad.ichthyo.mm | 1644 ++++++++--------- 9 files changed, 933 insertions(+), 850 deletions(-) diff --git a/src/vault/gear/activity.hpp b/src/vault/gear/activity.hpp index 169f65b77..a17531f08 100644 --- a/src/vault/gear/activity.hpp +++ b/src/vault/gear/activity.hpp @@ -149,7 +149,7 @@ namespace gear { enum Proc {PASS ///< pass on the activation down the chain ,SKIP ///< skip rest of the Activity chain for good ,WAIT ///< nothing to do; wait and re-check for work later - ,KILL ///< obliterate the complete Activity-Term and all its dependencies + ,KICK ///< back pressure; get out of the way but be back soon ,HALT ///< abandon this play / render process }; @@ -628,7 +628,6 @@ namespace gear { * @return activity::Proc indication how to proceed with execution * - activity::PASS continue with regular processing of `next` * - activity::SKIP ignore the rest of the chain, look for new work - * - activity::KILL abort this complete Activity term (play change) * - activity::HALT serious problem, stop the Scheduler */ template diff --git a/src/vault/gear/scheduler-commutator.hpp b/src/vault/gear/scheduler-commutator.hpp index 7a64f0460..d76718de3 100644 --- a/src/vault/gear/scheduler-commutator.hpp +++ b/src/vault/gear/scheduler-commutator.hpp @@ -250,7 +250,7 @@ namespace gear { * - activity::PASS continue processing in regular operation * - activity::WAIT nothing to do now, check back later * - activity::HALT serious problem, cease processing - * - activity::SKIP to contend (spin) on GroomingToken + * - activity::KICK to contend (spin) on GroomingToken * @note Attempts to acquire the GroomingToken for immediate * processing, but not for just enqueuing planned tasks. * Never drops the GroomingToken explicitly (unless when @@ -264,7 +264,7 @@ namespace gear { ,SchedulerInvocation& layer1 ) { - if (!event) return activity::SKIP; + if (!event) return activity::KICK; Time now = executionCtx.getSchedTime(); sanityCheck (event, now); diff --git a/src/vault/gear/scheduler.hpp b/src/vault/gear/scheduler.hpp index 3231c65ca..abb08f232 100644 --- a/src/vault/gear/scheduler.hpp +++ b/src/vault/gear/scheduler.hpp @@ -670,6 +670,7 @@ namespace gear { * @return how to proceed further with this worker * - activity::PASS indicates to proceed or call back immediately * - activity::SKIP causes to exit this round, yet call back again + * - activity::KICK signals contention (not emitted here) * - activity::WAIT exits and places the worker into sleep mode * @note as part of the regular work processing, this function may * place the current thread into a short-term targeted sleep. diff --git a/src/vault/gear/work-force.cpp b/src/vault/gear/work-force.cpp index 55b944480..9be35598d 100644 --- a/src/vault/gear/work-force.cpp +++ b/src/vault/gear/work-force.cpp @@ -62,5 +62,38 @@ namespace gear { return util::max (std::thread::hardware_concurrency() , MINIMAL_CONCURRENCY); } + + + /** + * This is part of the weak level of anti-contention measures. + * When a worker is kicked out from processing due to contention, the immediate + * reaction is to try again; if this happens repeatedly however, increasingly strong + * delays are interspersed. Within the _weak zone,_ a short spinning wait is performed, + * and then the thread requests a `yield()` from the OS scheduler; this cycle is repeated. + */ + void + work::performRandomisedSpin (size_t stepping, size_t randFact) + { + size_t degree = CONTEND_SOFT_FACTOR * (1+randFact) * stepping; + for (volatile size_t i=0; i 0); + uint factor = 1u << (stepping-1); + return (CONTEND_WAIT + 10us*randFact) * factor; + } + }} // namespace vault::gear diff --git a/src/vault/gear/work-force.hpp b/src/vault/gear/work-force.hpp index 2a9ff1b1e..b1fc99e2f 100644 --- a/src/vault/gear/work-force.hpp +++ b/src/vault/gear/work-force.hpp @@ -35,7 +35,7 @@ ** Some parameters and configuration is provided to the workers, notably a _work functor_ ** invoked actively to »pull« work. The return value from this `doWork()`-function governs ** the worker's behaviour, either by prompting to pull further work, by sending a worker - ** into a sleep cycle, or even asking the worker to terminate. + ** into a sleep cycle, perform contention mitigation, or even asking the worker to terminate. ** ** @warning concurrency and synchronisation in the Scheduler (which maintains and operates ** WorkForce) is based on the assumption that _all maintenance and organisational @@ -80,7 +80,20 @@ namespace gear { namespace { - const double MAX_OVERPROVISIONING = 3.0; ///< safety guard to prevent catastrophic overprovisioning + const double MAX_OVERPROVISIONING = 3.0; ///< safety guard to prevent catastrophic over-provisioning + + const size_t CONTEND_SOFT_LIMIT = 3; ///< zone for soft anti-contention measures, counting continued contention events + const size_t CONTEND_STARK_LIMIT = CONTEND_SOFT_LIMIT + 5; ///< zone for stark measures, performing a sleep with exponential stepping + const size_t CONTEND_SATURATION = CONTEND_STARK_LIMIT + 4; ///< upper limit for the contention event count + const size_t CONTEND_SOFT_FACTOR = 100; ///< base counter for a spinning wait loop + const size_t CONTEND_RANDOM_STEP = 11; ///< stepping for randomisation of anti-contention measures + const microseconds CONTEND_WAIT = 100us; ///< base time unit for the exponentially stepped-up sleep delay in case of contention + + inline size_t + thisThreadHash() + { + return std::hash{} (std::this_thread::get_id()); + } } namespace work { ///< Details of WorkForce (worker pool) implementation @@ -101,13 +114,18 @@ namespace gear { { static size_t COMPUTATION_CAPACITY; - const milliseconds IDLE_WAIT = 20ms; - const size_t DISMISS_CYCLES = 100; + const milliseconds IDLE_WAIT = 20ms; ///< wait period when a worker _falls idle_ + const size_t DISMISS_CYCLES = 100; ///< number of idle cycles after which the worker terminates static size_t getDefaultComputationCapacity(); }; + + void performRandomisedSpin (size_t,size_t); + microseconds steppedRandDelay(size_t,size_t); + + using Launch = lib::Thread::Launch; /*************************************//** @@ -151,10 +169,15 @@ namespace gear { activity::Proc res = CONF::doWork(); if (emergency.load (std::memory_order_relaxed)) break; + if (res == activity::KICK) + res = contentionWait(); + else + if (kickLevel_) + --kickLevel_; if (res == activity::WAIT) res = idleWait(); else - idleCycles = 0; + idleCycles_ = 0; if (res != activity::PASS) break; } @@ -172,8 +195,8 @@ namespace gear { activity::Proc idleWait() { - ++idleCycles; - if (idleCycles < CONF::DISMISS_CYCLES) + ++idleCycles_; + if (idleCycles_ < CONF::DISMISS_CYCLES) { sleep_for (CONF::IDLE_WAIT); return activity::PASS; @@ -181,7 +204,33 @@ namespace gear { else // idle beyond threshold => terminate worker return activity::HALT; } - size_t idleCycles{0}; + size_t idleCycles_{0}; + + + activity::Proc + contentionWait() + { + if (not randFact_) + randFact_ = thisThreadHash() % CONTEND_RANDOM_STEP; + + if (kickLevel_ <= CONTEND_SOFT_LIMIT) + for (uint i=0; i check{0}; + { // ▽▽▽▽ regular work-cycles without delay + WorkForce wof{setup ([&]{ ++check; return activity::PASS; })}; + wof.incScale(); + sleep_for(5ms); + } + uint cyclesPASS{check}; + check = 0; + { // ▽▽▽▽ signals »contention« + WorkForce wof{setup ([&]{ ++check; return activity::KICK; })}; + wof.incScale(); + sleep_for(5ms); + } + uint cyclesKICK{check}; + CHECK (cyclesKICK < cyclesPASS); + CHECK (cyclesKICK < 50); + } + + + /** @test when a worker is sent into sleep-cycles for an extended time, * the worker terminates itself. */ diff --git a/wiki/renderengine.html b/wiki/renderengine.html index 08ece4aae..9ef7a8414 100644 --- a/wiki/renderengine.html +++ b/wiki/renderengine.html @@ -7302,7 +7302,7 @@ The primary scaling effects exploited to achieve this level of performance are t The way other parts of the system are built, requires us to obtain a guaranteed knowledge of some job's termination. It is possible to obtain that knowledge with some limited delay, but it nees to be absoultely reliable (violations leading to segfault). The requirements stated above assume this can be achieved through //jobs with guaranteed execution.// Alternatively we could consider installing specific callbacks -- in this case the scheduler itself has to guarantee the invocation of these callbacks, even if the corresponding job fails or is never invoked. It doesn't seem there is any other option. -
+
The Scheduler //maintains a ''Work Force'' (a pool of workers) to perform the next [[render activities|RenderActivity]] continuously.//
 Each worker runs in a dedicated thread; the Activities are arranged in a way to avoid blocking those worker threads
 * IO operations are performed asynchronously {{red{planned as of 9/23}}}
@@ -7317,6 +7317,7 @@ Moreover, the actual computation tasks, which can be parallelised, are at least
 The behaviour of individual workers is guided solely by the return-value flag from the work-functor. Consequently, no shared flags and no direct synchronisation whatsoever is required //within the {{{WorkForce}}} implementation.// -- notwithstanding the fact that the implementation //within the work-functor// obviously needs some concurrency coordination to produce these return values, since the whole point is to invoke this functor concurrently. The following aspects of worker behaviour can be directed:
 * returning {{{activity::PASS}}} instructs the worker to re-invoke the work-functor in the same thread immediately
 * returning {{{activity::WAIT}}} requests an //idle-wait cycle//
+* returning {{{activity::KICK}}} signals //contention,// causing a short back-off
 * any other value, notably {{{activity::HALT}}} causes the worker to terminate
 * likewise, an exception from anywhere within the worker shall terminate the worker and activate a »disaster mode«
 Essentially this implies that //the workers themselves// (not some steering master) perform the management code leading to the aforementioned state directing return codes.
diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm
index 48b3d78fa..353671da0 100644
--- a/wiki/thinkPad.ichthyo.mm
+++ b/wiki/thinkPad.ichthyo.mm
@@ -86432,7 +86432,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -88915,6 +88915,11 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + @@ -88937,9 +88942,16 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + - + + @@ -89033,6 +89045,79 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + + + +

+ das Erlangen des Token und die damit verbundene Barrier können durchaus 30µs Verzögerung für das ganze System bedingen. Auch die reinen Lesezugriffe werden unter Contention langsamer (was vermutlich an der x86-Architektur liegt) +

+ + +
+
+ + + + + + +

+ ....und dieser Zustand kann schon mal wenige ms lang andauern +

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

+ Die Konstanten sind derzeit fest eincompiliert, aber man kann die Wirkzonen und die Stärke des Effekts in weiten Grenzen steuern +

+

+     const size_t CONTEND_SOFT_LIMIT  = 3;                         ///< zone for soft anti-contention measures, counting continued contention events +

+

+     const size_t CONTEND_STARK_LIMIT = CONTEND_SOFT_LIMIT + 5;    ///< zone for stark measures, performing a sleep with exponential stepping +

+

+     const size_t CONTEND_SATURATION  = CONTEND_STARK_LIMIT + 4;   ///< upper limit for the contention event count +

+

+     const size_t CONTEND_SOFT_FACTOR = 100;                       ///< base counter for a spinning wait loop +

+

+     const size_t CONTEND_RANDOM_STEP = 11;                        ///< stepping for randomisation of anti-contention measures +

+

+     const microseconds CONTEND_WAIT = 100us;                      ///< base time unit for the exponentially stepped-up sleep delay in case of contention +

+ +
+ + + + + @@ -89040,6 +89125,18 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + +

+ ...bis die Situation in der Engine insgesamt klarer ist — das kann noch weit über das »Playback Vertical Slice« hinaus dauern, bis wir tatsächlich ein dynamisches Geschehen beobachten können +

+ +
+ + +
@@ -89060,7 +89157,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -89197,6 +89294,14 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + @@ -89281,9 +89386,10 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + @@ -89396,7 +89502,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -89407,6 +89513,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + @@ -105206,21 +105315,19 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + - + - - - +

dieses Prinzip könnte man aufweichen — ich halte das aber für eine schlechte Idee, denn der Scheduler ist auch so schon schwer genug zu verstehen, auch die Activity-Language beginnt schon zu »bröseln« ... gefährliche Tendenz. Meine Einschätzung ist, wir planen ehr so 10 Jobs pro Metajob-Lauf, d.h. das wären dann Größenordnung 500-1000µs Zeit, in der jedwede andere Management-Tätigkeit geblockt ist (renderjobs laufen trotzdem weiter). Sofern diese Annahme nicht zutrifft, möchte ich dafür konkrete Beweise sehen @@ -105235,9 +105342,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

commit 5c6354882de1be63724221022b374bd559a499b0 @@ -105303,9 +105408,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

aber sie dann auch zusätzlich in das post()  eingesetzt @@ -105319,9 +105422,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

...dann erlangt er es, und behält es @@ -105374,9 +105475,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

und: die Allokation findet hier statt @@ -105384,9 +105483,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

    term_ = move( @@ -105405,9 +105502,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

...eben wenn jemand von außen kommt... das ist i.d.R. nur beim seedCalcStream() der Fall @@ -105429,9 +105524,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

zum einen ist das nicht genau das feste Raster, und außerdem braucht der OS-Scheduler sowiso immer etwas länger @@ -105447,9 +105540,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

sage bewußt »dokumentieren«, denn diese Logik hier ist derart einfach zu implementieren, daß eigentlich kein Test notwendig wäre @@ -105460,9 +105551,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

das ist nämlich unverhältnismäßig schwer im Test zu verifizieren, und steht in keinem Verhältnis zur Aufgabe @@ -105489,8 +105578,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + @@ -105500,9 +105589,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

Assertion-Failure: der Aufrufer von dropGroomingToken() hält es nicht @@ -105510,763 +105597,11 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - - - - - - -

- vault::gear::SchedulerCommutator::dropGroomingToken()+0xeb -

-

- -

-

- vault::gear::SchedulerCommutator::ScopedGroomingGuard::~ScopedGroomingGuard()+0x27 -

-

- -

-

- vault::gear::ScheduleSpec::post()+0x1b8 -

-

- -

-

- vault::gear::test::TestChainLoad<16ul>::ScheduleCtx::disposeStep(unsigned long, unsigned long)+0x22d -

-

- -

-

- vault::gear::test::TestChainLoad<16ul>::ScheduleCtx::ScheduleCtx(vault::gear::test::TestChainLoad<16ul>&, vault::gear::Scheduler&)::{lambda(unsigned long, unsigned long)#1}::operator()(unsigned long, unsigned long) const+0x2e -

-

- -

-

- std::_Function_handler<void (unsigned long, unsigned long), vault::gear::test::TestChainLoad<16ul>::ScheduleCtx::ScheduleCtx(vault::gear::test::TestChainLoad<16ul>&, vault::gear::Scheduler&)::{lambda(unsigned long, unsigned long)#1}>::_M_invoke(std::_Any_data const&, unsigned long&&, std::_Any_data const&)+0x52 -

-

- -

-

- std::function<void (unsigned long, unsigned long)>::operator()(unsigned long, unsigned long) const+0x61 -

-

- -

-

- vault::gear::test::RandomChainPlanFunctor<16ul>::invokeJobOperation(lumiera_jobParameter_struct const&)+0x29d -

-

- -

-

- vault::gear::Activity::invokeFunktor(lib::time::Time)+0x740 -

-

- -

-

- vault::gear::activity::Proc vault::gear::Activity::activate<vault::gear::Scheduler::ExecutionCtx>(lib::time::Time, vault::gear::Scheduler::ExecutionCtx&)+0x70 -

-

- -

-

- vault::gear::activity::Proc vault::gear::ActivityLang::activateChain<vault::gear::Scheduler::ExecutionCtx>(vault::gear::Activity*, vault::gear::Scheduler::ExecutionCtx&)+0x4e -

-

- -

-

- vault::gear::activity::Proc vault::gear::ActivityLang::dispatchChain<vault::gear::Scheduler::ExecutionCtx>(vault::gear::Activity*, vault::gear::Scheduler::ExecutionCtx&)+0x68 -

-

- -

-

- vault::gear::activity::Proc vault::gear::SchedulerCommutator::postDispatch<vault::gear::Scheduler::ExecutionCtx>(vault::gear::ActivationEvent, vault::gear::Scheduler::ExecutionCtx&, vault::gear::SchedulerInvocation&)+0xc1 -

-

- -

-

- vault::gear::Scheduler::postChain(vault::gear::ActivationEvent)+0x2a9 -

-

- -

-

- vault::gear::Scheduler::continueMetaJob(lib::time::Time, vault::gear::Job, vault::gear::ManifestationID)+0x164 -

-

- -

-

- vault::gear::Scheduler::seedCalcStream(vault::gear::Job, vault::gear::ManifestationID, lib::time::FrameRate)+0x8b -

-

- -

-

- vault::gear::test::TestChainLoad<16ul>::ScheduleCtx::performRun()+0x17e -

-

- -

-

- vault::gear::test::TestChainLoad<16ul>::ScheduleCtx::launch_and_wait()::{lambda()#1}::operator()() const+0x2a -

-

- -

-

- double lib::test::benchmarkTime<vault::gear::test::TestChainLoad<16ul>::ScheduleCtx::launch_and_wait()::{lambda()#1}>(vault::gear::test::TestChainLoad<16ul>::ScheduleCtx::launch_and_wait()::{lambda()#1} const&, unsigned long)+0x25 -

-

- -

-

- vault::gear::test::TestChainLoad<16ul>::ScheduleCtx::launch_and_wait()+0x25 -

-

- -

-

- vault::gear::test::SchedulerStress_test::smokeTest()+0x4af -

-

- -

- -
- - - - - - - - - - - - - - - -

-    ·‖ 20: @22281 HT:0  -> ∘ -

-

-    ·‖ 20: @22304 HT:0  -> ∘ -

-

-    ·‖ 0F: @22307 HT:0  -> ∘ -

-

- -

-

- ... dispose(i=46,lev:23) -> @23000 -

-

- -

-

-    ·‖ 5A: @22184 HT:0  -> ∘ -

-

-    ·‖ 18: @22217 HT:0  -> ∘ -

-

-    ·‖ 24: @22249 HT:0  -> ∘ -

-

-    ·‖ 5A: @22358 HT:0  -> ∘ -

-

-    ·‖ 7C: @22389 HT:0  -> ∘ -

-

-    ·‖ 24: @22402 HT:0  -> ∘ -

-

-    ·‖ 7C: @22414 HT:0  -> ∘ -

-

-    ·‖ 0F: @22361 HT:0  -> ∘ -

-

-    ·‖ 18: @22438 HT:0  -> ∘ -

-

-    ·‖ CD: @22456 HT:0  -> ∘ -

-

-    ·‖ F6: @22247 HT:0  -> ∘ -

-

-    ·‖ CD: @22486 HT:0  -> ∘ -

-

-    ·‖ 20: @22337 HT:0  -> ∘ -

-

-    ·‖ CD: @22507 HT:0  -> ∘ -

-

-    ·‖ 18: @22466 HT:0  -> ∘ -

-

-    ·‖ 24: @22528 HT:0  -> ∘ -

-

-    ·‖ F6: @22498 HT:0  -> ∘ -

-

-    ·‖ CD: @22548 HT:0  -> ∘ -

-

-    ·‖ CD: @22573 HT:0  -> ∘ -

-

-    ·‖ 0F: @22605 HT:0  -> ∘ -

-

-    ·‖ 7C: @22436 HT:0  -> ∘ -

-

-    ·‖ 0F: @22632 HT:0  -> ∘ -

-

-    ·‖ 7C: @22646 HT:0  -> ∘ -

-

-    ·‖ 5A: @22381 HT:0  -> ∘ -

-

-    ·‖ 0F: @22659 HT:0  -> ∘ -

-

-    ·‖ 7C: @22668 HT:0  -> ∘ -

-

-    ·‖ 24: @22552 HT:0  -> ∘ -

-

-    ·‖ 7C: @22691 HT:0  -> ∘ -

-

-    ·‖ CD: @22721 HT:0  -> ∘ -

-

-    ·‖ 0F: @22735 HT:0  -> ∘ -

-

-    ·‖ 5A: @22751 HT:0  -> ∘ -

-

-    ·‖ 0F: @22762 HT:0  -> ∘ -

-

-    ·‖ 5A: @22774 HT:0  -> ∘ -

-

-    ·‖ 7C: @22714 HT:0  -> ∘ -

-

-    ·‖ 5A: @22796 HT:0  -> ∘ -

-

-    ·‖ 7C: @22807 HT:0  -> ∘ -

-

-    ·‖ 5A: @22821 HT:0  -> ∘ -

-

-    ·‖ 7C: @22830 HT:0  -> ∘ -

-

-    ·‖ 18: @22836 HT:0  -> ∘ -

-

-    ·‖ 5A: @22844 HT:0  -> ∘ -

-

-    ·‖ CD: @22745 HT:0  -> ∘ -

-

-    ·‖ 5A: @22866 HT:0  -> ∘ -

-

-    ·‖ 20: @22575 HT:0  -> ∘ -

-

-    ·‖ 0F: @22897 HT:0  -> ∘ -

-

-    ·‖ 0F: @22927 HT:0  -> ∘ -

-

-    ·‖ 0F: @22952 HT:0  -> ∘ -

-

-    ·‖ 20: @22965 HT:0  -> ∘ -

-

-    ·‖ 7C: @22900 HT:0  -> ∘ -

-

-    ·‖ 0F: @22975 HT:0  -> ∘ -

-

-    ·‖ 20: @22986 HT:0  -> ∘ -

-

-    ·‖ CD: @22892 HT:0  -> ∘ -

-

-    ·‖ 5A: @23015 HT:0  -> ∘ -

-

-    ·‖ 24: @23020 HT:0  -> ∘ -

-

-    ·‖ 20: @23012 HT:0  -> ∘ -

-

-    ·‖ 20: @23088 HT:0  -> ∘ -

-

-    ·‖ 7C: @23106 HT:0  -> ∘ -

-

-    ·‖ F6: @22810 HT:0  -> ∘ -

-

-    ·‖ 20: @23118 HT:0  -> ∘ -

-

-    -

-

- ‖•△•‖     wof:8 HT:0 -

-

- ‖SCH‖ 0C: @23067 ○ start=23000 dead:30000 -

-

- -

-

-    ·‖ 20: @23149 HT:0  -> ∘ -

-

-    ·‖ 5A: @23099 HT:0  -> ∘ -

-

-    ·‖ 18: @22917 HT:0  -> ∘ -

-

-    ·‖ 20: @23174 HT:0  -> ∘ -

-

-    ·‖ 24: @23049 HT:0  -> ∘ -

-

-    ·‖ CD: @23157 HT:0  -> ∘ -

-

-    ·‖ 0F: @23231 HT:0  -> ∘ -

-

-    ·‖ 7C: @23127 HT:0  -> ∘ -

-

-    ·‖ F6: @23241 HT:0  -> ∘ -

-

-    ·‖ 0F: @23252 HT:0  -> ∘ -

-

-    ·‖ F6: @23265 HT:0  -> ∘ -

-

-    ·‖ 0F: @23276 HT:0  -> ∘ -

-

-    ·‖ 5A: @23296 HT:0  -> ∘ -

-

-    ·‖ 5A: @23318 HT:0  -> ∘ -

-

-    ·‖ 5A: @23339 HT:0  -> ∘ -

-

-    ·‖ 20: @23210 HT:0  -> ∘ -

-

-    ·‖ 0F: @23371 HT:0  -> ∘ -

-

-    ·‖ 24: @23383 HT:0  -> ∘ -

-

-    ·‖ 0F: @23395 HT:0  -> ∘ -

-

-    ·‖ 0F: @23412 HT:0  -> ∘ -

-

-    ·‖ F6: @23286 HT:0  -> ∘ -

-

-    ·‖ 18: @23309 HT:0  -> ∘ -

-

-    ·‖ CD: @23360 HT:0  -> ∘ -

-

-    ·‖ F6: @23455 HT:0  -> ∘ -

-

-    ·‖ 24: @23404 HT:0  -> ∘ -

-

-    ·‖ 0F: @23436 HT:81  -> ▶ 0 -

-

-    ·‖ 18: @23470 HT:81  -> ∘ -

-

-    -

-

- !◆!   0C: @23447  ⚙  calc(i=46, lev:23) -

-

- -

-

-    ·‖ 24: @23525 HT:81  -> ∘ -

-

-    ·‖ 5A: @23554 HT:81  -> ∘ -

-

- -

-

- !◆!   0C: @23510  ⤴       (i=46) -

-

- ..!   0F: @23537  ⚙  calc(i=0, lev:0) -

-

- -

-

-    ·‖ 24: @23553 HT:1000  -> ▶ 81 -

-

-    ·‖ F6: @23616 HT:1000  -> ∘ -

-

-    ·‖ 20: @23627 HT:1000  -> ∘ -

-

-    ·‖ CD: @23644 HT:1000  -> ∘ -

-

- -

-

- ‖▷▷▷‖ 24: @ 23658 HT:1000 -

-

- -

-

-    ·‖ 20: @23679 HT:1000  -> ∘ -

-

-    ·‖ F6: @23648 HT:1000  -> ∘ -

-

-    ·‖ 20: @23706 HT:1000  -> ∘ -

-

-    ·‖ 18: @23588 HT:1000  -> ∘ -

-

-    ·‖ F6: @23720 HT:1000  -> ∘ -

-

-    ·‖ 20: @23731 HT:1000  -> ∘ -

-

-    ·‖ 5A: @23617 HT:1000  -> ∘ -

-

-    ·‖ 24: @23739 HT:2000  -> ▶ 1000 -

-

-    ·‖ 7C: @23638 HT:1000  -> ∘ -

-

- -

-

- ... -

-

- -

-

- -

-

-    ·‖ 7C: @24156 HT:6000  -> ∘ -

-

- !◆!   24: @24126  ⚙  calc(i=1, lev:1) -

-

-    ·‖ F6: @24098 HT:5000  -> ∘ -

-

-    ·‖ 5A: @24185 HT:7000  -> ∘ -

-

-    ·‖ 5A: @24212 HT:7000  -> ∘ -

-

-    ·‖ 0F: @24168 HT:7000  -> ∘ -

-

-                 |n.(47,lev:23) -

-

-    ·‖ F6: @24258 HT:7000  -> ∘ -

-

-    ·‖ CD: @23881 HT:4000  -> ∘ -

-

-    ·‖ 7C: @24270 HT:7000  -> ∘ -

-

-    ·‖ 0F: @24314 HT:7000  -> ∘ -

-

-    ·‖ 7C: @24329 HT:7000  -> ∘ -

-

-    ·‖ 20: @24339 HT:7000  -> ∘ -

-

-    ·‖ 5A: @24241 HT:7000  -> ∘ -

-

-    ·‖ CD: @24316 HT:7000  -> ∘ -

-

-    ·‖ 5A: @24380 HT:7000  -> ∘ -

-

-    ·‖ 0F: @24341 HT:7000  -> ∘ -

-

-    ·‖ 5A: @24401 HT:7000  -> ∘ -

-

- ... dispose(i=47,lev:23) -> @23000 -

-

-    ·‖ 5A: @24431 HT:7000  -> ∘ -

-

-    ·‖ 7C: @24411 HT:7000  -> ∘ -

-

-    ·‖ 0F: @24470 HT:7000  -> ∘ -

-

- ..!   24: @24456  ⤴       (i=1) -

-

-    ·‖ 7C: @24498 HT:7000  -> ∘ -

-

- -

-

-    ·‖ 7C: @24522 HT:7000  -> ∘ -

-

- -

-

-    ·‖ 7C: @24543 HT:7000  -> ∘ -

-

-    ·‖ F6: @24284 HT:7000  -> ∘ -

-

-    ·‖ 7C: @24563 HT:7000  -> ∘ -

-

-    ·‖ 18: @24162 HT:7000  -> ▶ 6000 -

-

-    ·‖ F6: @24577 HT:7000  -> ∘ -

-

-    ·‖ 7C: @24585 HT:7000  -> ∘ -

-

-    ·‖ 5A: @24618 HT:7000  -> ∘ -

-

-    ·‖ 5A: @24642 HT:7000  -> ∘ -

-

- -

-

- ... -

-

- -

-

-    ·‖ F6: @30810 HT:23000  -> ∘ -

-

-    ·‖ 18: @30789 HT:23000  -> ∘ -

-

-    ·‖ 20: @30830 HT:23000  -> ∘ -

-

- -

-

- !◆!   0F: @30867  ⚙  calc(i=5, lev:5) -

-

- -

-

-    ·‖ 7C: @30858 HT:23000  -> ∘ -

-

-    ·‖ 24: @30882 HT:73658  -> ▶ 23000 -

-

- ..!   0F: @30974  ⤴       (i=5) -

-

- ‖PST‖ 0F: @31024 ◒ start=5000▹▹6000 dead:30000 -

-

-    ·‖ 0F: @31055 HT:73658  -> ▶ 6000 -

-

- -

-

- !◆!   0F: @31079  ⚙  calc(i=6, lev:6) -

-

- ‖•△•‖     wof:8 HT:73658 -

-

- ‖SCH‖ 0C: @31199 ○ start=23000 dead:30000 -

-

- -

-

- !◆!   0C: @31217  ⚙  calc(i=47, lev:23) -

-

- ..!   0F: @31876  ⤴       (i=6) -

-

- ‖PST‖ 0F: @31920 ◒ start=6000▹▹7000 dead:30000 -

-

-    ·‖ 0F: @31946 HT:73658  -> ▶ 7000 -

-

- -

-

- !◆!   0F: @31971  ⚙  calc(i=7, lev:7) -

-

- ..!   0C: @33137  ⤴       (i=47) -

-

- 0000000609: PRECONDITION: scheduler-commutator.hpp:155: thread_1: dropGroomingToken: (groomingToken_.load(memory_order_relaxed) == thisThread()) -

-

- -

- - -
- - - - - - - - - -

- da nun t > 23000, kann er selber in den Dispatch gehen -

- -
-
- - - - - - - - - - - - - -

- um @31217  führt er selber aus ⚙  calc(i=47, lev:23) -

- -
-
- - - - - @@ -106294,33 +105629,29 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - + - - - +

Forderung: ein echer Planer muß das erkennen und abbrechen

- -
+
- + - @@ -106330,13 +105661,44 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + +

+ in entsprechend analogen Situationen wird jetzt einfach wirr bis zu Ende geplant und irgendwo bleibt die Berechnung stecken... so soll's sein +

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

+ durch die veränderte Grooming-Token-Behandlung läuft der Planungs-Job nun exclusiv (wie es sein sollte); da aber für den Debug-Modus das pre-Roll-Stepping zu knapp gewählt ist, ist er noch +3ms über den geplanten Startpunkt hinaus aktiv (für den späteren Teil des Zeitplans, insofern kein Problem). Da die calculation-structure zu Beginn noch dünn ist, holt der Scheduler das bis Node-23 locker wieder rein +

+ +
+
+
@@ -106366,19 +105728,631 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - - - + + + + - + + + + + + + + + +

+ ALLE Signalmarken haben einen vierstelligen Namen, und dabei soll es bleiben. Beim Formulieren der Dokumentation fällt mir auf, daß KICK klarer ist, und sich besser in Formulierungen fügt. Man könnte allerdings diesen Zustand auch DAVE nennen, weil wir dem Worker sagen „cant do that, Dave“ +

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

+ if ret != PASS könnte jetzt logisch falsch sein +

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

+ und zwar wegen der jüngsten Änderung in λ-post; dieses stellt nun nur noch in die Queue und geht nicht mehr in den Dispatch — andererseits weiß ich, daß der neue Zustand nur an einer einzigen Stelle injiziert wird, nämlich wenn postChain zwar feststellt, daß es sofort in den Dispatch gehen soll, dann aber nichts von der Queue bekommt (weil das Grooming-Token nicht erlangt werden konnte) +

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

+ man würde damit einen Regelbreich schaffen können, den man über die Parametrisierung abstimmt; allerdings wirft das das Problem auf, wie man sowas beobachten, messen und kontrollieren kann (da es stark systemabhängig ist) +

+ + +
+
+ + + + + + +

+ Das läuft auf einen Effekt hinaus, der bei Wiederholung schnell so drastisch wird, daß der Worker mehr oder weniger »aus dem Verkehr« gezogen wird. Für diesen Ansatz spricht, daß er praktisch nicht von der konkreten System-Performance abhängig ist (mit Einschränkungen). Da wir ein Kurzzeit-Gedächtnis haben, wäre dieser Effekt dann aber auch nur mäßig »sticky«, was angemessen erscheint (im Grunde bräuchten wir eine Hysterese +

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

+ wegen einigen wenigen Contentions dürfen wir kein G'summs machen, in diesem unteren Bereich ist eigentlich nur die Randomisierung wichtig; ab einem gewissen Punkt müssen dann aber drastischere Maßnahmen folgen, sonst kann das System ersticken (wie ich jetzt im konkreten Testfall sehe) +

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

+ das ist doof ... weil unser Thread-Wrapper den Thread relativ spät erzeugt, und eben grade nicht aus der Initialiser-List; außerdem gibt es eine signifikante Verzögerung bis die Thread-ID da ist (hab da schon > 200µs gesehen) +

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

+ 67: INIT @0  rand=6 +

+

+ 67: ◁ @148 +

+

+ 67: △1  @196 +

+

+ 67: ◁ @258 +

+

+ 67: △2  @293 +

+

+ 67: △2  @340 +

+

+ 67: ◁ @387 +

+

+ 67: △3  @420 +

+

+ 67: △3  @470 +

+

+ 67: △3  @520 +

+

+ 67: ◁ @570 +

+

+ 67: ▲4  @603 +

+

+ 67: ◁ @893 +

+

+ 67: ▲5  @948 +

+

+ 67: ◁ @1387 +

+

+ 67: ▲6  @1441 +

+

+ 67: ◁ @2269 +

+

+ 67: ▲7  @2417 +

+

+ 67: ◁ @3888 +

+

+ 67: ▲8  @4028 +

+

+ 67: ◁ @6785 +

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

+ EA: INIT @0  rand=2 +

+

+ EA: ◁ @225 +

+

+ EA: △1  @280 +

+

+ EA: ◁ @368 +

+

+ EA: △2  @419 +

+

+ EA: △2  @482 +

+

+ EA: ◁ @544 +

+

+ EA: △3  @594 +

+

+ EA: △3  @657 +

+

+ EA: △3  @722 +

+

+ EA: ◁ @787 +

+

+ EA: ▲4  @836 +

+

+ EA: ◁ @1114 +

+

+ EA: ▲5  @1168 +

+

+ EA: ◁ @1529 +

+

+ EA: ▲6  @1581 +

+

+ EA: ◁ @2178 +

+

+ EA: ▲7  @2232 +

+

+ EA: ◁ @3398 +

+

+ EA: ▲8  @3521 +

+

+ EA: ◁ @5607 +

+

+ EA: ◁ @5719 +

+

+ EA: ◁ @5754 +

+

+ EA: ◁ @5787 +

+

+ EA: ◁ @5821 +

+

+ EA: ◁ @5852 +

+

+ EA: ◁ @5886 +

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

+ EF: INIT @0  rand=3 +

+

+ EF: ◁ @209 +

+

+ EF: △1  @260 +

+

+ EF: ◁ @333 +

+

+ EF: △2  @379 +

+

+ EF: △2  @440 +

+

+ EF: ◁ @499 +

+

+ EF: △3  @544 +

+

+ EF: △3  @607 +

+

+ EF: △3  @669 +

+

+ EF: ◁ @731 +

+

+ EF: ▲1  @794 +

+

+ EF: ◁ @1080 +

+

+ EF: ▲2  @1132 +

+

+ EF: ◁ @1508 +

+

+ EF: ▲3  @1556 +

+

+ EF: ◁ @2252 +

+

+ EF: ▲4  @2392 +

+

+ EF: ◁ @3615 +

+

+ EF: ▲5  @3789 +

+

+ EF: ◁ @6049 +

+

+ EF: ▲5  @6194 +

+

+ EF: ◁ @8410 +

+

+ EF: ▲5  @8509 +

+

+ EF: ◁ @10744 +

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

+ allein die Print-Statements können schon 100µs und mehr kosten, und vergiften den Cache. Trotzdem beobachte ich im Test(Debug-Mode) auch ohne die Print-statements etwa die gleiche Zahl Zyklen in 5ms +

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

+ das ist spannend... +

+

+ denn das bedeutet, daß der Scheduler eine zusätzliche Verzögerung von +3ms in diesem Fall einfach aufgefangen hat — vermutlich in der ersten Phase, in der noch »Luft« im Zeitplan ist, und daher die Abläufe im Wesentlichen durch die Trigger-Zeiten getacktet werden +

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

+ »normal« bedeutet, daß die Planung eines Jobs 100-200µs dauert +

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

+ dieser Worker taucht dann nur noch einmal auf, 20ms später; es ist aber nicht zu erkennen, warum (möglicherweise war er dazwischen da, aber es gab keine Arbeit) +

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

+ kann man nicht genau sagen, weil er beim 3.Mal schon wieder was bekommt; jedenfalls macht er keine auffälligen Verzögerungen +

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

+ das ist völlig unauffällig und etwa so wie vorher. An der Stelle sind im Doppelstrang die ersten Nodes mit einem höheren Gewicht; bis zu dem Punkt hat ein Worker die ganze Dependency-Kette allein abgearbeitet, während die beiden anderen Worker die restlichen (leeren) Jobs vom Schedule weggeräumt haben. Nun ist der erste Worker länger weg, und der 2. Worker greift mit geringer Verzögerung mit ein. +

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

+ Fazit: Schema funktioniert und wirkt wie erhofft +

+ +
+
@@ -106599,30 +106573,17 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- +

- ...diese beinhalten formal sogar zweimal einen postChain (das 2.mal wäre eigentlich stets redundant, ist aber formal sauberer). + Das wurde explizit so eingerichtet, um eine bessere Parallelisierung zu erziehlen: NOTIFY wird nur »abgeworfen«, ohne an dieser Stelle das Grooming-Token zu erlangen; erst wenn der Worker zurückkommt, findet er u.U die NOTIFY-continuations mit Priorität in der Queue (und zwar genau dann, wenn ihr Startzeitpunkt bereits in der Vergangenheit liegt)

- - - - - -

- ...selbst wenn es dringendere Tasks in der Queue gäbe... -

- -
- -
- - - + +
@@ -106765,7 +106726,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -106790,6 +106751,19 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + +

+ ...und zwar durch die Anpassungen am Notify und die Klarstellungen / Verschärfungen für das Grooming-Token; es zeigt sich, daß unter Contention die Worker einen active-Spin machen, der das System erheblich belasten kann +

+ +
+ + + +