Scheduler: complete and cover load indicator

- sample distance to scheduler head whenever a worker asks for work
- moving average with N = worker-pool size and damp-factor 2
- multiply with the current concurrency fraction
This commit is contained in:
Fischlurch 2023-10-30 23:29:10 +01:00
parent a087e52ab1
commit 9f7711d26b
4 changed files with 291 additions and 54 deletions

View file

@ -65,10 +65,19 @@
** assigning new tasks, while workers returning from idle state are typically
** sent back into idle state, unless there is direct need for more capacity.
**
** # Load indicator
** A fusion of some operational values is used to build a heuristic indicator
** of current scheduler load. These values can be retrieved with low overhead.
** - the fraction of maximal concurrency actually used
** - a sampling of the lag, i.e. the average distance to the next task;
** this observation is sampled whenever a worker asks for more work.
**
** @see scheduler.hpp
** @see SchedulerLoadControl_test
** @see SchedulerService_test::verify_LoadFactor()
** @see SchedulerStress_test
**
** @todo WIP-WIP-WIP 10/2023 »Playback Vertical Slice«
** @todo WIP-WIP 11/2023 »Playback Vertical Slice«
**
*/
@ -109,6 +118,7 @@ namespace gear {
using std::chrono_literals::operator ""us;
using std::function;
using std::atomic_int64_t;
using std::memory_order_relaxed;
namespace { // Scheduler default config
@ -122,12 +132,16 @@ namespace gear {
Duration SLEEP_HORIZON{_uTicks (20ms)};
Duration WORK_HORIZON {_uTicks ( 5ms)};
Duration NEAR_HORIZON {_uTicks (50us)};
const double LAG_SAMPLE_DAMPING = 2; ///< smoothing factor for exponential moving average of lag;
}
/**
* Controller to coordinate resource usage related to the Scheduler.
* - implements the schematics for capacity redistribution
* - provides some performance indicators, notably the LoadController::effectiveLoad()
* @todo WIP-WIP 10/2023 gradually filling in functionality as needed
* @see BlockFlow
* @see Scheduler
@ -161,32 +175,65 @@ namespace gear {
atomic_int64_t sampledLag_{0};
/**
* @internal evaluate the situation encountered when a worker calls for work.
* @remark this function updates an exponential moving average of schedule
* head distance in a concurrency safe way. The value sampled is
* clamped to prevent poisoning by excess peaks.
* @warning Called from a hot path, with the potential to create congestion.
* Measurements indicate single call < 200ns and < 5µs when contended.
*/
void
markLagSample (Time head, Time now)
{
double headroom = _raw(std::clamp<TimeVar> (now - (head.isRegular()? head:now)
, -SLEEP_HORIZON
, WORK_HORIZON));
const int64_t N = wiring_.maxCapacity * 3;
int64_t average = sampledLag_.load (std::memory_order_relaxed);
{ // negative when free capacity
double lag = _raw(std::clamp<TimeVar> (now - (head.isRegular()? head:now)
, -SLEEP_HORIZON
, WORK_HORIZON));
const double alpha = LAG_SAMPLE_DAMPING / (1 + wiring_.maxCapacity);
int64_t average = sampledLag_.load (memory_order_relaxed);
int64_t newAverage;
do newAverage = std::floor ((headroom + (N-1)*average) / N);
while (not sampledLag_.compare_exchange_weak (average, newAverage, std::memory_order_relaxed));
do newAverage = std::floor (lag*alpha + (1-alpha)*average);
while (not sampledLag_.compare_exchange_weak (average, newAverage, memory_order_relaxed));
}
public:
/**
* @return guess of current scheduler pressure
* @remark the value is sampled at the points where workers pull work.
* Since these »capacity events« happen randomly, the current
* distance to the schedule head hints at either free headroom
* or overload leading to congestion.
* @see #markLagSample
*/
int64_t
lag()
averageLag() const
{
return sampledLag_.load (std::memory_order_relaxed);
return sampledLag_.load (memory_order_relaxed);
}
/**
* @internal (re)set the currently seen average lag.
* @return the previous average value
* @remark intended for unit testing and state reset;
* thread-save. Regular use not recommended.
*/
int64_t
setCurrentAverageLag (int64_t lag)
{
return sampledLag_.exchange(lag, memory_order_relaxed);
}
/**
* @return guess of current load relative to full load
* @remark based on the fusion of several state values,
* which can be retrieved with low overhead
* - the used fraction of possible concurrency
* - [sampling of distance to next task](\ref averageLag)
*/
double
effectiveLoad()
effectiveLoad() const
{
double lag = sampledLag_.load (std::memory_order_relaxed);
double lag = sampledLag_.load (memory_order_relaxed);
lag -= 200;
lag /= _raw(WORK_HORIZON);
lag *= 10;

View file

@ -34,9 +34,12 @@
#include "lib/test/diagnostic-output.hpp"/////////////////////TODO
//#include <utility>
#include <chrono>
using test::Test;
using std::move;
using std::chrono::microseconds;
//using util::isSameObject;
@ -71,6 +74,7 @@ namespace test {
tendNextActivity();
classifyCapacity();
scatteredReCheck();
indicateAverageLoad();
walkingDeadline();
}
@ -288,6 +292,88 @@ namespace test {
/** @test verify fusion of sampled observations to guess average scheduler load
* - use a rigged wiring of the load controller to verify calculation
* based on known values of current _concurrency_ and _schedule pressure_
* - scheduling on average 200µs behind nominal schedule is considered
* the regular balanced state and thus defined as 100% schedule pressure
* - if congestion builds up to 1/10 of WORK_HORIZON, 200% overload is indicated
* - on the other hand, if workers appear on average 200µs before the typical
* balanced state, the resulting headroom is defined to constitute 50% pressure
* - the pressure value is multiplied with the degree of concurrency
* - the pressure is sampled from the lag (distance of current time to the
* next activity to schedule), which is observed whenever a worker
* calls in to retrieve more work. These calls happen randomly.
* @todo WIP 10/23 define implement
*/
void
indicateAverageLoad()
{
uint maxThreads = 10;
uint currThreads = 0;
LoadController::Wiring setup;
setup.maxCapacity = maxThreads;
setup.currWorkForceSize = [&]{ return currThreads; };
// rigged setup to verify calculated load indicator
LoadController lctrl{move(setup)};
CHECK (0 == lctrl.averageLag());
CHECK (0 == lctrl.effectiveLoad());
// Manipulate the sampled average lag (in µs)
lctrl.setCurrentAverageLag (200);
// Scheduling 200µs behind nominal start time -> 100% schedule pressure
currThreads = 5;
CHECK (0.5 == lctrl.effectiveLoad());
currThreads = 8;
CHECK (0.8 == lctrl.effectiveLoad());
currThreads = 10;
CHECK (1.0 == lctrl.effectiveLoad());
// congestion +500µs -> 200% schedule pressure
lctrl.setCurrentAverageLag (200+500);
CHECK (2.0 == lctrl.effectiveLoad());
lctrl.setCurrentAverageLag (200+500+500);
CHECK (3.0 == lctrl.effectiveLoad()); // -> 300%
// if average headroom 500µs -> 50% load
lctrl.setCurrentAverageLag (200-500);
CHECK (0.5 == lctrl.effectiveLoad());
CHECK (-300 == lctrl.averageLag());
lctrl.setCurrentAverageLag (200-500-500-500);
CHECK (0.25 == lctrl.effectiveLoad());
CHECK (-1300 == lctrl.averageLag());
// load indicator is always modulated by concurrency level
currThreads = 2;
CHECK (0.05 == lctrl.effectiveLoad());
// average lag is sampled from the situation when workers call in
Time head = Time::ZERO;
TimeVar curr = Time{1,0};
lctrl.markIncomingCapacity (head,curr);
CHECK (-882 == lctrl.averageLag());
lctrl.markIncomingCapacity (head,curr);
CHECK (-540 == lctrl.averageLag());
curr = Time{0,1};
lctrl.markIncomingCapacity (head,curr);
lctrl.markIncomingCapacity (head,curr);
CHECK (1291 == lctrl.averageLag());
curr = head - Time{0,2};
lctrl.markIncomingCapacity (head,curr);
CHECK (-2581 == lctrl.averageLag());
}
/** @test TODO
* @todo WIP 10/23 🔁 define implement
*/

View file

@ -167,7 +167,7 @@ SHOW_EXPR(wuff())
sleep_for(50us);
cout << wuff() << " +++ Load: "<<scheduler.getLoadIndicator()
<<" --- HT= "<<_raw(scheduler.layer1_.headTime())-_raw(wau)
<<" -+- Lag "<< scheduler.loadControl_.lag()
<<" -+- Lag "<< scheduler.loadControl_.averageLag()
<<endl;
}
}

View file

@ -82636,12 +82636,10 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1698632383220" ID="ID_716182887" MODIFIED="1698632491040" TEXT="Last-Sch&#xe4;tzung entwickeln">
<arrowlink COLOR="#4a5982" DESTINATION="ID_846411589" ENDARROW="Default" ENDINCLINATION="-55;-589;" ID="Arrow_ID_1233923536" STARTARROW="None" STARTINCLINATION="-390;272;"/>
<icon BUILTIN="pencil"/>
<node CREATED="1698632501888" ID="ID_334185073" MODIFIED="1698632518646" TEXT="1. Idee : WorkForce::size()">
<node COLOR="#435e98" CREATED="1698632501888" FOLDED="true" ID="ID_334185073" MODIFIED="1698715205196" TEXT="1. Idee : WorkForce::size()">
<node CREATED="1698632520658" ID="ID_1308650674" MODIFIED="1698632625312" TEXT="funktioniert so lala">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<head/>
<body>
<p>
...weil wir im Moment immer mit Vollast einsteigen und dann zwar im Idle-Fall herunterregeln; letzteres passiert typischerweise aber schnell. Also im Test sehe ich daher immer nur 1.0 oder keine Last
@ -82652,9 +82650,7 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</node>
<node CREATED="1698632626540" ID="ID_1025237859" MODIFIED="1698632714226" TEXT="k&#xf6;nnte trotzdem brauchbar sein">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<head/>
<body>
<p>
...das kann aber erst beurteilt werden, wenn wir echte Last-Szenarien kennen. Denkbar w&#228;re, da&#223; z.B. ein real-Time Render in etwa mit 3 Cores &#252;ber die Runden kommt
@ -82666,13 +82662,11 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<arrowlink COLOR="#ce6c70" DESTINATION="ID_167635013" ENDARROW="Default" ENDINCLINATION="14;-26;" ID="Arrow_ID_1543325365" STARTARROW="None" STARTINCLINATION="-53;3;"/>
</node>
</node>
<node CREATED="1698632730462" ID="ID_167635013" MODIFIED="1698632819293" TEXT="2. Idee : den Lag auswerten">
<node COLOR="#435e98" CREATED="1698632730462" FOLDED="true" ID="ID_167635013" MODIFIED="1698715199604" TEXT="2. Idee : den Lag auswerten">
<linktarget COLOR="#ce6c70" DESTINATION="ID_167635013" ENDARROW="Default" ENDINCLINATION="14;-26;" ID="Arrow_ID_1543325365" SOURCE="ID_348864601" STARTARROW="None" STARTINCLINATION="-53;3;"/>
<node BACKGROUND_COLOR="#f8f1cb" COLOR="#a50125" CREATED="1698632823807" ID="ID_1604616715" MODIFIED="1698632937183" TEXT="das ist aber gar nicht so einfach">
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1698632823807" ID="ID_1604616715" MODIFIED="1698702752645" TEXT="das ist aber gar nicht so einfach">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<head/>
<body>
<ul>
<li>
@ -82689,7 +82683,7 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</html></richcontent>
<icon BUILTIN="messagebox_warning"/>
</node>
<node COLOR="#435e98" CREATED="1698632941116" ID="ID_1797377910" MODIFIED="1698633204991" TEXT="kommt man irgendwie an Werte pro Worker heran?">
<node COLOR="#435e98" CREATED="1698632941116" FOLDED="true" ID="ID_1797377910" MODIFIED="1698633204991" TEXT="kommt man irgendwie an Werte pro Worker heran?">
<icon BUILTIN="help"/>
<node CREATED="1698632978397" ID="ID_1774631238" MODIFIED="1698632990815" TEXT="im Worker speichern scheide ich sofort aus (Architektur)"/>
<node CREATED="1698632992259" ID="ID_1128001448" MODIFIED="1698633006765" TEXT="mit Thread-Locals will ich schon gleich gar nicht anfangen"/>
@ -82703,19 +82697,18 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<node CREATED="1698633186745" ID="ID_1822633925" MODIFIED="1698633194284" TEXT="dar&#xfc;ber dann ein moving-Average"/>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1698633206582" ID="ID_1221695664" MODIFIED="1698633222669" TEXT="moving-Average in concurrent environment">
<icon BUILTIN="flag-yellow"/>
<node COLOR="#338800" CREATED="1698633206582" FOLDED="true" ID="ID_1221695664" MODIFIED="1698711102459" TEXT="moving-Average in concurrent environment">
<icon BUILTIN="button_ok"/>
<node CREATED="1698633226641" ID="ID_1247903339" MODIFIED="1698633242622" TEXT="die &#xbb;capacity events&#xab; sind wirklich zuf&#xe4;llig"/>
<node CREATED="1698633248600" ID="ID_1947820475" MODIFIED="1698633269818" TEXT="Grooming-Token kommt nicht in Frage, Locking sowiso nicht"/>
<node CREATED="1698633270718" ID="ID_1128407195" MODIFIED="1698633298798" TEXT="als fortlaufendes Produkt mit einem Atomic berechnen">
<icon BUILTIN="idea"/>
<node COLOR="#5b280f" CREATED="1698633270718" ID="ID_1128407195" MODIFIED="1698702657581" TEXT="als fortlaufendes Produkt mit einem Atomic berechnen">
<arrowlink COLOR="#ef3052" DESTINATION="ID_1504851010" ENDARROW="Default" ENDINCLINATION="-204;13;" ID="Arrow_ID_339066446" STARTARROW="None" STARTINCLINATION="81;212;"/>
<icon BUILTIN="button_cancel"/>
<node CREATED="1698633317512" ID="ID_1161198928" MODIFIED="1698633425867" TEXT="Moving-Average l&#xe4;&#xdf;t sich in Faktoren-Form bringen">
<linktarget COLOR="#757ecb" DESTINATION="ID_1161198928" ENDARROW="Default" ENDINCLINATION="1079;62;" ID="Arrow_ID_29993738" SOURCE="ID_649514706" STARTARROW="None" STARTINCLINATION="760;42;"/>
<node CREATED="1698633491729" HGAP="44" ID="ID_1503820730" MODIFIED="1698633786583" TEXT="mu&#xdf; dazu aber aktuelles MA herausdividieren" VSHIFT="-6">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<head/>
<body>
<p>
<u>&#160;Exponential MA</u>:
@ -82739,9 +82732,7 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</node>
<node CREATED="1698633300801" ID="ID_614548490" MODIFIED="1698633874064" TEXT="Grundidee: &#xbb;eventual consistency&#xab;">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<head/>
<body>
<p>
Bei einem moving-Average interessiert es uns eigentlich nicht, <i>wann genau </i>der Wert gilt und <i>f&#252;r wen </i>er gilt. Es interessiert uns nur, was am Ende rausgekommen ist, bzw. wie der Trend im Moment so ist
@ -82751,9 +82742,7 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</node>
<node CREATED="1698633875244" ID="ID_202660501" MODIFIED="1698633929103">
<richcontent TYPE="NODE"><html>
<head>
</head>
<head/>
<body>
<p>
wir ziehen das aktuelle MA &#187;jetzt&#171;
@ -82770,9 +82759,7 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</node>
<node COLOR="#5b280f" CREATED="1698633939212" ID="ID_1007287132" MODIFIED="1698635722966">
<richcontent TYPE="NODE"><html>
<head>
</head>
<head/>
<body>
<p>
und <i>das </i>l&#228;&#223;t sich mit <b>zwei </b>Atomic-Operationen realisieren
@ -82780,17 +82767,36 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</body>
</html></richcontent>
<icon BUILTIN="button_cancel"/>
<node CREATED="1698635724493" ID="ID_1504851010" MODIFIED="1698635742694" TEXT="Nein! es gibt keine atomare Multiplikation"/>
<node CREATED="1698635745173" ID="ID_509243439" MODIFIED="1698635754653" TEXT="damit mu&#xdf; ich doch die compare-exchange-Schleife machen"/>
<node CREATED="1698635724493" ID="ID_1504851010" MODIFIED="1698702651541" TEXT="Nein! es gibt keine atomare Multiplikation">
<linktarget COLOR="#ef3052" DESTINATION="ID_1504851010" ENDARROW="Default" ENDINCLINATION="-204;13;" ID="Arrow_ID_339066446" SOURCE="ID_1128407195" STARTARROW="None" STARTINCLINATION="81;212;"/>
<icon BUILTIN="stop-sign"/>
</node>
<node CREATED="1698702601241" ID="ID_987600121" MODIFIED="1698702624304" TEXT="(w&#xe4;re auch nicht wirklich sinnvoll...)">
<richcontent TYPE="NOTE"><html>
<head/>
<body>
<p>
wenn man auch nur ansatzweise bedenkt, wie Assembly funktioniert
</p>
</body>
</html></richcontent>
</node>
<node CREATED="1698635745173" ID="ID_509243439" MODIFIED="1698702739866" TEXT="damit mu&#xdf; ich doch die compare-exchange-Schleife machen">
<arrowlink COLOR="#4b5f99" DESTINATION="ID_995319675" ENDARROW="Default" ENDINCLINATION="311;-25;" ID="Arrow_ID_1824704831" STARTARROW="None" STARTINCLINATION="-347;17;"/>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1698633984534" ID="ID_526389523" MODIFIED="1698634002948" TEXT="Prototyp-Implementierung in die markIncomingCapcity einh&#xe4;ngen">
<icon BUILTIN="flag-yellow"/>
<node COLOR="#435e98" CREATED="1698702687206" ID="ID_995319675" MODIFIED="1698702733706" TEXT="dann optimistic-compare-exchange">
<linktarget COLOR="#4b5f99" DESTINATION="ID_995319675" ENDARROW="Default" ENDINCLINATION="311;-25;" ID="Arrow_ID_1824704831" SOURCE="ID_509243439" STARTARROW="None" STARTINCLINATION="-347;17;"/>
<icon BUILTIN="idea"/>
</node>
</node>
<node COLOR="#338800" CREATED="1698633984534" FOLDED="true" ID="ID_526389523" MODIFIED="1698704884755" TEXT="Prototyp-Implementierung in die markIncomingCapcity einh&#xe4;ngen">
<icon BUILTIN="button_ok"/>
<node CREATED="1698635767135" ID="ID_1284337606" MODIFIED="1698635791023" TEXT="erst einmal nur den Offset samplen und beobachten"/>
<node CREATED="1698635791606" ID="ID_1832030937" MODIFIED="1698635805382" TEXT="Berechnung per compare-and-swap"/>
<node CREATED="1698638460422" ID="ID_583392778" MODIFIED="1698638471065" TEXT="Werte sehen plausibel aus"/>
<node COLOR="#435e98" CREATED="1698638471844" ID="ID_429172374" MODIFIED="1698679552921" TEXT="wird aber extrem h&#xe4;ufig aufgerufen &#x2014; warum?">
<node COLOR="#435e98" CREATED="1698638471844" FOLDED="true" ID="ID_429172374" MODIFIED="1698679552921" TEXT="wird aber extrem h&#xe4;ufig aufgerufen &#x2014; warum?">
<arrowlink COLOR="#ab2254" DESTINATION="ID_1087870120" ENDARROW="Default" ENDINCLINATION="-356;12;" ID="Arrow_ID_187678307" STARTARROW="None" STARTINCLINATION="215;13;"/>
<icon BUILTIN="broken-line"/>
<node COLOR="#5b280f" CREATED="1698638515487" ID="ID_847572680" MODIFIED="1698677275734" TEXT="sitzt in einem Poll-Zyklus?!">
@ -82837,17 +82843,54 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</node>
</node>
</node>
<node CREATED="1698680053860" ID="ID_1477905589" MODIFIED="1698680070366" TEXT="Microbenchmark der Me&#xdf;-Funktion">
<node CREATED="1698680053860" ID="ID_1477905589" MODIFIED="1698680311278">
<richcontent TYPE="NODE"><html>
<head/>
<body>
<p>
<b>Microbenchmark</b>&#160;der Me&#223;-Funktion
</p>
</body>
</html></richcontent>
<icon BUILTIN="info"/>
<node CREATED="1698680080248" ID="ID_1604376595" MODIFIED="1698680100226" TEXT="100&apos;000 Iterationen (pro thread)"/>
<node CREATED="1698680071746" ID="ID_1915767027" MODIFIED="1698680266483" TEXT="single-threaded: 120ns"/>
<node CREATED="1698680116412" ID="ID_1012490645" MODIFIED="1698680136077" TEXT="7-threads-contending: 4.5&#xb5;s"/>
</node>
</node>
</node>
<node COLOR="#338800" CREATED="1698702792264" ID="ID_803243904" MODIFIED="1698702815999" TEXT="Verlauf sieht im Test sinnvoll aus">
<icon BUILTIN="button_ok"/>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1697808402431" ID="ID_1499932709" MODIFIED="1697808419725" TEXT="beobachtet / wei&#xdf; von der WorkForce-St&#xe4;rke">
<icon BUILTIN="flag-yellow"/>
<node COLOR="#338800" CREATED="1698702824964" ID="ID_28379545" MODIFIED="1698715179796" TEXT="Last-Sch&#xe4;tzung aus Parallel-Load und Lag">
<icon BUILTIN="button_ok"/>
<node COLOR="#338800" CREATED="1698702851630" ID="ID_936369816" MODIFIED="1698702859151" TEXT="vorl&#xe4;ufige Implementierung">
<icon BUILTIN="button_ok"/>
</node>
<node CREATED="1698711855235" ID="ID_855137031" LINK="#ID_945067314" MODIFIED="1698711885087" TEXT="Smoothing-Faktor &#x3b1; geeignet w&#xe4;hlen"/>
<node COLOR="#338800" CREATED="1698702859767" ID="ID_1863816756" MODIFIED="1698715174255" TEXT="Berechnung per Einzeltest abdecken">
<arrowlink COLOR="#6a9eb2" DESTINATION="ID_1985241613" ENDARROW="Default" ENDINCLINATION="1958;-153;" ID="Arrow_ID_13582151" STARTARROW="None" STARTINCLINATION="598;41;"/>
<icon BUILTIN="button_ok"/>
</node>
</node>
</node>
<node COLOR="#338800" CREATED="1697808402431" ID="ID_1499932709" MODIFIED="1698715221160" TEXT="beobachtet / wei&#xdf; von der WorkForce-St&#xe4;rke">
<icon BUILTIN="button_ok"/>
<node COLOR="#338800" CREATED="1698715222158" ID="ID_1634688486" MODIFIED="1698715238526" TEXT="realisiert per function-binding">
<icon BUILTIN="button_ok"/>
</node>
<node COLOR="#338800" CREATED="1698715228810" ID="ID_404234345" MODIFIED="1698715283830" TEXT="im Scheduler verdrahtet">
<richcontent TYPE="NOTE"><html>
<head/>
<body>
<p>
<font face="Monospaced" color="#372493">Scheduler::connectMonitoring()</font>
</p>
</body>
</html></richcontent>
<icon BUILTIN="button_ok"/>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1698544547801" ID="ID_1011726550" MODIFIED="1698544832553" TEXT="regelm&#xe4;&#xdf;ige Integration / state fusion">
@ -88848,6 +88891,46 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<node COLOR="#338800" CREATED="1698243357550" ID="ID_458370410" MODIFIED="1698243362335" TEXT="scatteredReCheck">
<icon BUILTIN="button_ok"/>
</node>
<node COLOR="#338800" CREATED="1698702871326" ID="ID_1985241613" MODIFIED="1698715174256" TEXT="indicateAverageLoad">
<linktarget COLOR="#6a9eb2" DESTINATION="ID_1985241613" ENDARROW="Default" ENDINCLINATION="1958;-153;" ID="Arrow_ID_13582151" SOURCE="ID_1863816756" STARTARROW="None" STARTINCLINATION="598;41;"/>
<icon BUILTIN="button_ok"/>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1698711912906" HGAP="45" ID="ID_1431349753" MODIFIED="1698715153652" TEXT="der Test ist schwierig einzurichten" VSHIFT="-3">
<richcontent TYPE="NOTE"><html>
<head/>
<body>
<p>
Und zwar weil wir
</p>
<ul>
<li>
ein Moving-Average im Spiel haben, das es sehr schwer macht, bestimmte Werte zu erzielen
</li>
<li>
das Moving-Average in einer privaten Variablen steckt (ist auch gut so, da es ein Atomic sein mu&#223; wegen concurrency)
</li>
</ul>
</body>
</html></richcontent>
<icon BUILTIN="messagebox_warning"/>
</node>
<node COLOR="#435e98" CREATED="1698712060968" HGAP="29" ID="ID_16893514" MODIFIED="1698715151332" TEXT="die beiden Belange trennen">
<icon BUILTIN="yes"/>
<node COLOR="#338800" CREATED="1698712079191" ID="ID_860323615" MODIFIED="1698715158820" TEXT="f&#xfc;hre einen Setter ein f&#xfc;r den Test">
<richcontent TYPE="NOTE"><html>
<head/>
<body>
<p>
Alternative w&#228;re, den Test zum Friend zu machen. Das will ich aber m&#246;glichst nicht zur Gewohnheit werden lassen. Hier k&#246;nnte man das umgehen, da wegen der Concurrency ein Setter sogar sinnvoll ist: der w&#252;rde dann einen atomic-swap machen
</p>
</body>
</html></richcontent>
<icon BUILTIN="button_ok"/>
</node>
<node COLOR="#338800" CREATED="1698712234889" ID="ID_1843404710" MODIFIED="1698715161154" TEXT="dann kann man den Moving-Average einmal isoliert demonstrieren">
<icon BUILTIN="button_ok"/>
</node>
</node>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1688336835085" ID="ID_1753236898" MODIFIED="1697732777759" TEXT="SchedulerService_test">
@ -89497,9 +89580,7 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<node CREATED="1698627399974" ID="ID_1608280091" MODIFIED="1698628172170" TEXT="ExecutionCtx::post : 1.3&#xb5;s"/>
<node COLOR="#5b280f" CREATED="1698628174302" ID="ID_127791676" MODIFIED="1698628251530" TEXT="das ist aber nicht sonderlich hilfreich">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<head/>
<body>
<p>
...weil es eben ein Microbenchmark des reinen Dispatch-Aufrufs ist, und ohne das ganze Drumherum mit Grooming-Token, Queue-Verwaltung und Kapazit&#228;ts-Verteilung
@ -90830,6 +90911,29 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</body>
</html></richcontent>
<arrowlink COLOR="#757ecb" DESTINATION="ID_1161198928" ENDARROW="Default" ENDINCLINATION="1079;62;" ID="Arrow_ID_29993738" STARTARROW="None" STARTINCLINATION="760;42;"/>
<node CREATED="1698711155793" HGAP="37" ID="ID_945067314" MODIFIED="1698711686721" TEXT="D&#xe4;mpfungsfaktor &#x3b1;" VSHIFT="11">
<richcontent TYPE="NOTE"><html>
<head/>
<body>
<p>
EMA(i) = &#945; &#183; value(i) + (1- &#945;) &#183; EMA(i-1)
</p>
<p>
</p>
<p>
Im Grunde gibt es keine Herleitung, denn das ganze Verfahren ist pragmatisch.
</p>
<p>
In meiner Rationalisierung w&#228;re dann &#945; &#8788; 1/N&#160;&#160;&#10233; 1-&#945; = (N-1)/N
</p>
<p>
In der Wirtschaft (Chart-Analyse) findet man oft &#945; &#8788; damp / (N+1)&#160;&#160;und damp &#8788; 2 ist die gel&#228;ufige Wahl. Damit kommt man auf die oft zitierte Faustregel, da&#223; ein neuer Wert in einen 10-day EMA mit 18% eingeht (2/11)
</p>
</body>
</html></richcontent>
<icon BUILTIN="idea"/>
</node>
</node>
<node CREATED="1689355234696" ID="ID_1358310078" MODIFIED="1689355261867" TEXT="Betrachtung des Einzelwerts in der k-ten Iteration">
<node CREATED="1689355265862" ID="ID_19716247" MODIFIED="1689355311124" TEXT="x&#x2096; = x/N &#xb7; ((N-1)/N)^k"/>