Scheduler: implement and verify random reshuffling of capacity

...using the current time itself as source for randomisation;
the test indicates this yields a smooth and even distribution.
This commit is contained in:
Fischlurch 2023-10-24 03:59:27 +02:00
parent 3eaf623e98
commit 1d5b8c3e9c
4 changed files with 178 additions and 81 deletions

View file

@ -111,16 +111,17 @@ namespace gear {
return TimeValue{us.count()};
}
Duration SLEEP_HORIZON{_uTicks (20ms)};
Duration WORK_HORIZON {_uTicks ( 5ms)};
Duration NOW_HORIZON {_uTicks (50us)};
}
/**
* Controller to coordinate resource usage related to the Scheduler.
* @todo WIP-WIP 10/2023 just a placeholder for now
* @todo WIP-WIP 10/2023 gradually filling in functionality as needed
* @see BlockFlow
* @see Scheduler
*/
@ -131,6 +132,7 @@ namespace gear {
struct Wiring
{
size_t maxCapacity{2};
///////TODO add here functors to access performance indicators
};
explicit
@ -146,8 +148,8 @@ namespace gear {
const Wiring wiring_;
TimeVar tendedHead_{Time::ANYTIME};
public:
public:
/**
* did we already tend for the indicated next head time?
* @note const and non-grooming
@ -163,6 +165,7 @@ namespace gear {
* @remark while this is just implemented as simple state,
* the meaning is that some free capacity has been directed
* towards that time, and thus further capacity go elsewhere.
* @warning must hold the grooming-Token to use this mutation.
*/
void
tendNext (Time nextHead)
@ -170,14 +173,16 @@ namespace gear {
tendedHead_ = nextHead;
}
enum
Capacity {DISPATCH ///< sent to work
,TENDNEXT ///< reserved for next task
,SPINTIME ///< awaiting imminent activities
,NEARTIME ///< capacity for active processing required
,WORKTIME ///< typical stable work task rhythm expected
,IDLETIME ///< time to go to sleep
};
/** Allocation of capacity to time horizon of expected work */
enum Capacity {DISPATCH ///< sent to work
,TENDNEXT ///< reserved for next task
,SPINTIME ///< awaiting imminent activities
,NEARTIME ///< capacity for active processing required
,WORKTIME ///< typical stable work task rhythm expected
,IDLETIME ///< time to go to sleep
};
/** classification of time horizon for scheduling */
static Capacity
@ -191,6 +196,8 @@ namespace gear {
}
/** decide how this thread's capacity shall be used
* after it returned from being actively employed */
Capacity
markOutgoingCapacity (Time head, Time now)
{
@ -200,6 +207,8 @@ namespace gear {
: horizon;
}
/** decide how this thread's capacity shall be used
* when returning from idle wait and asking for work */
Capacity
markIncomingCapacity (Time head, Time now)
{
@ -209,13 +218,30 @@ namespace gear {
}
/**
* Generate a time offset to relocate currently unused capacity
* to a time range where it's likely to be needed. Assuming the
* classification is based on the current distance to the next
* Activity known to the scheduler (the next tended head time).
* - for capacity immediately to be dispatched this function
* will not be used, yet returns logically sound values.
* - after the next head time has been tended for, free capacity
* should be relocated into a time span behind that point
* - the closer the next head time, the more focused this relocation
* - but each individual delay is randomised within those time bounds,
* to produce an even »flow« of capacity on average. Randomisation
* relies on a hash (bit rotation) of current time, broken down
* to the desired time horizon.
*/
Offset
scatteredDelayTime (Time now, Capacity capacity)
{
auto scatter = [&](Duration horizon)
{
size_t step = 1;////////////////////////////////////////////////////TODO implement randomisation
return Offset{_raw(horizon) * step / wiring_.maxCapacity};
gavl_time_t wrap = hash_value(now) % _raw(horizon);
ENSURE (0 <= wrap and wrap < _raw(horizon));
return TimeValue{wrap};
};
switch (capacity) {
@ -230,7 +256,7 @@ namespace gear {
case WORKTIME:
return Offset{tendedHead_-now + scatter(SLEEP_HORIZON)};
case IDLETIME:
return /*without start offset*/ scatter(SLEEP_HORIZON);
return Offset{/*no base offset*/ scatter(SLEEP_HORIZON)};
default:
NOTREACHED ("uncovered work capacity classification.");
}

View file

@ -31,6 +31,7 @@
//#include "lib/time/timevalue.hpp"
//#include "lib/format-cout.hpp"
//#include "lib/util.hpp"
#include "lib/test/diagnostic-output.hpp"/////////////////////TODO
//#include <utility>
@ -70,8 +71,8 @@ namespace test {
tendNextActivity();
classifyCapacity();
scatteredReCheck();
walkingDeadline();
setupLalup();
}
@ -82,6 +83,7 @@ namespace test {
simpleUsage()
{
LoadController ctrl;
/////////////////////////TODO a simple usage example focusing on load diagnostics
}
@ -233,30 +235,55 @@ namespace test {
/** @test verify the re-distribution of free capacity by targeted delay
* @todo WIP 10/23 🔁 define implement
* - the implementation uses the next-tended start time as anchor point
* - capacity classes which should be scheduled right away will actually
* never call this function yet still a sensible value is returned here
* - capacity targeted at current work will be redistributed behind the
* next-tended time, and within a time span corresponding to the work realm
* - capacity targeted towards more future work will be distributed within
* the horizon defined by the sleep-cycle
* - especially for capacity sent to sleep, this redistribution works
* without being shifted behind the next-tended time, since in that case
* the goal is to produce a random distribution of the »sleeper« callbacks.
* - the offset is indeed randomised, using current time for randomisation
* @see LoadController::scatteredDelayTime()
* @todo WIP 10/23 define implement
*/
void
scatteredReCheck()
{
Wiring setup;
setup.maxCapacity = 16;
LoadController lctrl{move(setup)};
auto isBetween = [](auto lo, auto hi, auto val)
auto is_between = [](auto lo, auto hi, auto val)
{
return lo <= val and val < hi;
};
LoadController lctrl;
TimeVar now = RealClock::now();
Time next{now + FSecs(10)};
lctrl.tendNext (next);
Offset ten{FSecs(10)};
Time next{now + ten};
lctrl.tendNext(next);
CHECK (Time::ZERO == lctrl.scatteredDelayTime (now, Capacity::DISPATCH) );
CHECK (Time::ZERO == lctrl.scatteredDelayTime (now, Capacity::SPINTIME) );
CHECK ( next == lctrl.scatteredDelayTime (now, Capacity::TENDNEXT) );
CHECK (isBetween ( next, next+WORK_HORIZON , lctrl.scatteredDelayTime (now, Capacity::NEARTIME)));
CHECK (isBetween ( next, next+SLEEP_HORIZON, lctrl.scatteredDelayTime (now, Capacity::WORKTIME)));
CHECK (isBetween (Time::ZERO, SLEEP_HORIZON , lctrl.scatteredDelayTime (now, Capacity::IDLETIME)));
}
CHECK ( ten == lctrl.scatteredDelayTime (now, Capacity::TENDNEXT) );
CHECK (is_between ( ten, ten+ WORK_HORIZON, lctrl.scatteredDelayTime (now, Capacity::NEARTIME)));
CHECK (is_between ( ten, ten+SLEEP_HORIZON, lctrl.scatteredDelayTime (now, Capacity::WORKTIME)));
CHECK (is_between (Time::ZERO, SLEEP_HORIZON, lctrl.scatteredDelayTime (now, Capacity::IDLETIME)));
// Offset is randomised based on the current time
// Verify this yields an even distribution
double avg{0};
const size_t REPETITIONS = 1e6;
for (size_t i=0; i< REPETITIONS; ++i)
avg += _raw(lctrl.scatteredDelayTime (RealClock::now(), Capacity::IDLETIME));
avg /= REPETITIONS;
auto expect = _raw(SLEEP_HORIZON)/2;
auto error = fabs(avg/expect - 1);
CHECK (0.001 > error); // observing a quite stable skew ~ 0.4‰ on my system
} // let's see if this error bound triggers eventually...
/** @test TODO
@ -267,16 +294,6 @@ namespace test {
{
UNIMPLEMENTED ("walking Deadline");
}
/** @test TODO
* @todo WIP 10/23 🔁 define implement
*/
void
setupLalup()
{
}
};

View file

@ -7167,7 +7167,7 @@ Later on we expect a distinct __query subsystem__ to emerge, presumably embeddin
&amp;rarr; QuantiserImpl</pre>
</div>
<div title="Scheduler" creator="Ichthyostega" modifier="Ichthyostega" created="202304140131" modified="202309101341" tags="Rendering spec draft" changecount="19">
<div title="Scheduler" creator="Ichthyostega" modifier="Ichthyostega" created="202304140131" modified="202310240237" tags="Rendering spec draft" changecount="24">
<pre>//Invoke and control the dependency and time based execution of [[render jobs|RenderJob]]//
The Scheduler acts as the central hub in the implementation of the RenderEngine and coordinates the //processing resources// of the application. Regarding architecture, the Scheduler is located in the Vault-Layer and //running// the Scheduler is equivalent to activating the »Vault Subsystem«. An EngineFaçade acts as entrance point, providing high-level render services to other parts of the application: [[render jobs|RenderJob]] can be activated under various timing and dependency constraints. Internally, the implementation is organised into two layers:
;Layer-2: Coordination
@ -7184,6 +7184,9 @@ This leads to the observation that every render or playback process has to deal
The time-based ordering and prioritisation of [[render activities|RenderActivity]] is thus used as a //generic medium and agent// to support and implement complex interwoven computational tasks. On the layer-1 mentioned above, a combination of a lock-free dispatch queue is used, feeding into a single threaded priority queue organised by temporal deadlines. Most render activities are lightweight and entail quick updates to some state flags, while certain activities are extremely long running -- and those are shifted into worker threads based on priority.
&amp;rarr; [[the Activity-Language|RenderActivity]]
&amp;rarr; [[implementing Activities|RenderOperationLogic]]
!!!Managing the Load
At large, these considerations hint at a diverse and unspecific range of usages -- necessitating to rely on organisational schemes able to adapt to a wide array of load patterns, including sudden and drastic changes. Moreover the act of scheduling involves operating on time scales spanning several orders of magnitude, from dispatching and cascading notifications working on the µs scale up to a planning horizon for render calculations reaching out into a dimension of several seconds. And while the basic construction of the scheduler can be made to //behave correct// and accommodate without failure, it seems way more challenging to establish an arrangement of functionality able to yield good performance on average. Certainly this goal can not be achieved in one step, and by a single layer of implementation. Rather a second layer of operational control may be necessary to keep the machinery within suitable limits, possibly even some degree of dynamic optimisation of working parameters.
&amp;rarr; [[Load management and operational control|SchedulerLoadControl]]
!Usage pattern
The [[Language of render activities|RenderActivity]] forms the interface to the scheduler -- new activities are defined as //terms// and handed over to the scheduler. This happens as part of the ongoing job planning activities -- and thus will be performed //from within jobs managed by the scheduler.// Thus the access to the scheduler happens almost entirely from within the scheduler's realm itself, and is governed by the usage scheme of the [[Workers|SchedulerWorker]].
@ -7198,6 +7201,13 @@ The Scheduler is now considered an implementation-level facility with an interfa
&amp;rarr; [[Workers|SchedulerWorker]]
</pre>
</div>
<div title="SchedulerLoadControl" creator="Ichthyostega" modifier="Ichthyostega" created="202310240240" modified="202310240251" tags="Rendering spec operational draft" changecount="3">
<pre>The scheduling mechanism //requires active control of work parameters to achieve good performance on average.//
In a nutshell, the scheduler arranges planned [[render activities|RenderActivity]] onto a time axis -- and is complemented by an [[active »work force«|SchedulerWorker]] to //pull and retrieve// the most urgent next task when free processing capacity becomes available. This arrangement shifts focus from the //management of tasks// towards the //management of capacity// -- which seems more adequate, given that capacity is scarce while tasks are abundant, yet limited in size small and atomic.
This change of perspective however implies that not the time-axis is in control; rather processing is driven by the workers, which happen to show up in a pattern assumed to be //essentially random.// This leads to the conclusion that capacity may not be available at the point where it's needed most, and some overarching yet slowly adapting scheme of redistribution is required to make the render engine run smoothly.
</pre>
</div>
<div title="SchedulerMemory" creator="Ichthyostega" modifier="Ichthyostega" created="202307031622" modified="202307220323" tags="Rendering operational draft" changecount="29">
<pre>//The Scheduler uses an »Extent« based memory management scheme known as {{{BlockFlow}}}.//
The organisation of rendering happens in terms of [[Activities|RenderActivity]], which may bound by //dependencies// and limited by //deadlines.// For the operational point of view this implies that a sequence of allocations must be able to „flow through the Scheduler“ -- in fact, only references to these {{{Activity}}}-records are passed, while the actual descriptors reside at fixed memory locations. This is essential to model the dependencies and conditional execution structures efficiently. At some point however, any {{{Activity}}}-record will either be //performed// or //obsoleted// -- and this leads to the idea of managing the allocations in //extents// of memory here termed as »Epochs«

View file

@ -81981,9 +81981,7 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</node>
<node CREATED="1698082341487" ID="ID_689287390" MODIFIED="1698082650781" TEXT="Situationen und m&#xf6;gliche L&#xf6;sungen liegen dicht beieinander, aber...">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<head/>
<body>
<p>
...unterscheiden sich doch im Detail
@ -82278,9 +82276,7 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<icon BUILTIN="flag-yellow"/>
<node CREATED="1698090000881" ID="ID_713507066" MODIFIED="1698090231201">
<richcontent TYPE="NODE"><html>
<head>
</head>
<head/>
<body>
<p>
hier k&#246;nnte man <i>named arguments </i>gebrauchen....
@ -82292,9 +82288,7 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</node>
<node CREATED="1698090246167" ID="ID_719284884" MODIFIED="1698090309744">
<richcontent TYPE="NODE"><html>
<head>
</head>
<head/>
<body>
<p>
Parametrisierung: maxCapacity = <font face="Monospaced" color="#868686">work::Config::</font><font face="Monospaced" color="#3f2c83">COMPUTATION_CAPACITY</font>
@ -82310,9 +82304,7 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<icon BUILTIN="help"/>
<node CREATED="1698090381330" ID="ID_1787407613" MODIFIED="1698090452668" TEXT="Grooming-Behandlung hier&#xfc;ber integrieren?">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<head/>
<body>
<p>
schwierige Frage....
@ -82725,10 +82717,10 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<node CREATED="1697983591252" ID="ID_777679840" MODIFIED="1697983596782" TEXT="liefert activity::PROC"/>
<node CREATED="1697983597603" ID="ID_1430310887" MODIFIED="1697983605069" TEXT="f&#xfc;hrt die Verz&#xf6;gerung unmittelbar aus"/>
</node>
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1697983546449" ID="ID_1801117774" MODIFIED="1698079545463" TEXT="scatteredDelayTime()">
<icon BUILTIN="forward"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1698093070813" ID="ID_940807400" MODIFIED="1698093078487" TEXT="Parameter">
<icon BUILTIN="flag-yellow"/>
<node COLOR="#338800" CREATED="1697983546449" ID="ID_1801117774" MODIFIED="1698110860930" TEXT="scatteredDelayTime()">
<icon BUILTIN="button_ok"/>
<node BACKGROUND_COLOR="#c8c0b6" COLOR="#435e98" CREATED="1698093070813" ID="ID_940807400" MODIFIED="1698110872107" TEXT="Parameter">
<icon BUILTIN="info"/>
<node CREATED="1698093149583" ID="ID_1430906257" MODIFIED="1698093152974" TEXT="inhaltlich ben&#xf6;tigt">
<node CREATED="1698093101030" ID="ID_1059393148" MODIFIED="1698093114592" TEXT="Startpunkt"/>
<node CREATED="1698093082201" ID="ID_1834606392" MODIFIED="1698093166710" TEXT="Zeitspanne"/>
@ -82753,36 +82745,30 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<icon BUILTIN="info"/>
<node CREATED="1698093411613" ID="ID_1048787060" MODIFIED="1698093507603" TEXT="Genauigkeit ist grenzwertig">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<head/>
<body>
<p>
Die Zeitangaben im std::chrono-Framework reichen bis in den Nano-Bereich, und es gibt einen high-precision-Timer
</p>
</body>
</html>
</richcontent>
</html></richcontent>
</node>
<node CREATED="1698093418798" ID="ID_1005113032" MODIFIED="1698097058330" TEXT="aber f&#xfc;r derzeitige &#x26a0; Systeme hinreichend">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<head/>
<body>
<p>
...andererseits wei&#223; ich, da&#223; man schon einfachste Scheduling-Delays ab mindestens 400ns mi&#223;t, und da&#223; das Starten eines Thread auf meinem System mindestens 100&#181;s braucht. Der aktuelle Scheduler unter Linux (CFS) verwendet keine festen Time-Slices mehr, aber man versucht definitiv die Kontext-Switches zu minimieren. Latenzen oder Scheduling-Zyklen f&#252;r normale (nicht-realtime)-Prozesse liegen im Bereich <b>von Millisekunden</b>. Andererseits arbeitet die C++-Chrono-Funktion sleep_for nachweislich <i>im Schnitt</i>&#160;bis in den zweistelligen &#181;s-Bereich genau
</p>
</body>
</html>
</richcontent>
</html></richcontent>
</node>
<node CREATED="1698097066647" ID="ID_486321067" MODIFIED="1698097088638" TEXT="Anwendungszweck ist Kapazit&#xe4;ts-Verteilung">
<icon BUILTIN="idea"/>
</node>
</node>
</node>
<node COLOR="#435e98" CREATED="1698097112840" ID="ID_512066576" MODIFIED="1698097575285" TEXT="Fazit: bei Lumiera-Zeittypen bleiben">
<node BACKGROUND_COLOR="#c8c0b6" COLOR="#435e98" CREATED="1698097112840" ID="ID_512066576" MODIFIED="1698110883121" TEXT="Fazit: bei Lumiera-Zeittypen bleiben">
<arrowlink COLOR="#8494c7" DESTINATION="ID_1504658001" ENDARROW="Default" ENDINCLINATION="-3062;-49;" ID="Arrow_ID_1741608203" STARTARROW="None" STARTINCLINATION="1460;105;"/>
<icon BUILTIN="yes"/>
</node>
@ -82794,41 +82780,36 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<node CREATED="1698097848806" ID="ID_1030706126" MODIFIED="1698097864752" TEXT="dagegen den Bezugspunkt kann man aus tended-next gewinnen"/>
<node CREATED="1698097910006" ID="ID_1800439542" MODIFIED="1698100816307">
<richcontent TYPE="NODE"><html>
<head>
</head>
<head/>
<body>
<p>
die Funktion liefert eine <b>Duration</b>
</p>
</body>
</html>
</richcontent>
</html></richcontent>
<arrowlink DESTINATION="ID_735254473" ENDARROW="Default" ENDINCLINATION="-57;47;" ID="Arrow_ID_806787669" STARTARROW="None" STARTINCLINATION="69;10;"/>
<icon BUILTIN="forward"/>
<node CREATED="1698097935810" ID="ID_1458098203" MODIFIED="1698097945269" TEXT="in &#xb5;-Tick-Aufl&#xf6;sung"/>
<node CREATED="1698097946017" ID="ID_1013671224" MODIFIED="1698097965475" TEXT="&#x27f6; chrono::microseconds"/>
<node CREATED="1698100685330" ID="ID_735254473" MODIFIED="1698100816307" TEXT="naja... Offset geht auch...">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<head/>
<body>
<p>
...und spart einen zus&#228;tzlichen clamp-Schritt und den Absolutwert (den das OS sowiso machen wird)
</p>
</body>
</html>
</richcontent>
</html></richcontent>
<linktarget COLOR="#a9b4c1" DESTINATION="ID_735254473" ENDARROW="Default" ENDINCLINATION="-57;47;" ID="Arrow_ID_806787669" SOURCE="ID_1800439542" STARTARROW="None" STARTINCLINATION="69;10;"/>
<icon BUILTIN="smiley-oh"/>
</node>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1698101257233" ID="ID_254738750" MODIFIED="1698101462139" TEXT="Implementierung">
<icon BUILTIN="pencil"/>
<node COLOR="#338800" CREATED="1698101262893" ID="ID_664525403" MODIFIED="1698101459058" TEXT="l&#xe4;uft auf eine Fallunterscheidung hinaus">
<node COLOR="#338800" CREATED="1698101257233" ID="ID_254738750" MODIFIED="1698110850996" TEXT="Implementierung">
<icon BUILTIN="button_ok"/>
<node COLOR="#338800" CREATED="1698101262893" ID="ID_664525403" MODIFIED="1698110953787" TEXT="l&#xe4;uft auf eine Fallunterscheidung hinaus">
<linktarget COLOR="#6391ac" DESTINATION="ID_664525403" ENDARROW="Default" ENDINCLINATION="-10;127;" ID="Arrow_ID_225157022" SOURCE="ID_1719824145" STARTARROW="None" STARTINCLINATION="118;-6;"/>
<icon BUILTIN="button_ok"/>
<node CREATED="1698101300800" ID="ID_1888321367" MODIFIED="1698101337767" TEXT="jetzt &#x27fc; nix"/>
<node CREATED="1698101338355" ID="ID_43153407" MODIFIED="1698101370639" TEXT="TENDNEXT ist logischerweise &#x27fc; &#x394; next"/>
@ -82836,10 +82817,70 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<node CREATED="1698101394396" ID="ID_144988262" MODIFIED="1698101446121" TEXT="WORKTIME &#x27fc; &#x394; + scatter SLEEP_HORIZON"/>
<node CREATED="1698101413705" ID="ID_1574790196" MODIFIED="1698101438499" TEXT="IDLETIME &#x27fc; ebenso, aber ohne &#x394;"/>
</node>
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1698101272892" ID="ID_826560450" MODIFIED="1698101293954" TEXT="Randomisierung per Zeit">
<icon BUILTIN="flag-pink"/>
<node COLOR="#338800" CREATED="1698101272892" ID="ID_826560450" MODIFIED="1698110848117" TEXT="Randomisierung per Zeit">
<icon BUILTIN="button_ok"/>
<node COLOR="#5b280f" CREATED="1698108232842" ID="ID_737192144" MODIFIED="1698108261094" TEXT="Zeit modulo HARDWARE_CONCURRENCY">
<icon BUILTIN="button_cancel"/>
<node CREATED="1698108263654" ID="ID_292936973" MODIFIED="1698108267158" TEXT="keine gute Idee"/>
<node CREATED="1698108267669" ID="ID_638201196" MODIFIED="1698108293589" TEXT="k&#xf6;nnte unebene Verteilung bekommen"/>
<node CREATED="1698108294114" ID="ID_1682704197" MODIFIED="1698108384494" TEXT="wegen Grid-Effekten des OS-Schedulings">
<richcontent TYPE="NOTE"><html>
<head/>
<body>
<p>
es ist nicht auszuschlie&#223;en, da&#223; <i>gewisse End-Bits h&#228;ufiger auftreten</i>, weil die OS-Aufrufe / Scheduler-Aktivit&#228;ten eben doch mit einer gewissen Regelm&#228;&#223;igkeit stattfinden
</p>
</body>
</html></richcontent>
</node>
</node>
<node COLOR="#5b280f" CREATED="1698108387445" ID="ID_446153361" MODIFIED="1698108415322" TEXT="Zeitwert einfach modulo Zeitspanne">
<icon BUILTIN="button_cancel"/>
<node CREATED="1698108417489" ID="ID_703108540" MODIFIED="1698108422892" TEXT="auch keine gute Idee"/>
<node CREATED="1698108423432" ID="ID_1152223683" MODIFIED="1698108437706" TEXT="starke Korrelation zum urspr&#xfc;nglichen Zeitwert"/>
<node CREATED="1698108438294" ID="ID_1777804515" MODIFIED="1698108470745" TEXT="Anfragen nah beinander &#x27f6; schlechte Verteilung"/>
</node>
<node COLOR="#338800" CREATED="1698108472248" ID="ID_1857586122" MODIFIED="1698108484801" TEXT="also die Zeit vorher hashen">
<icon BUILTIN="yes"/>
<node COLOR="#5b280f" CREATED="1698108593601" ID="ID_416588142" MODIFIED="1698108614561" TEXT="also std::hash&lt;gavl_time_t&gt; verwenden">
<icon BUILTIN="button_cancel"/>
</node>
<node CREATED="1698108491671" ID="ID_440232057" MODIFIED="1698108592155" TEXT="Ha! habe vor einiger Zeit schon eine Hashfunktion implementiert">
<richcontent TYPE="NOTE"><html>
<head/>
<body>
<p>
Im Zusammenhang mit dem JobTicket (in der Tat, da braucht man einen ordentlichen hash-Key aus beliebiger Zeit)
</p>
<p>
Siehe TimeValue_test::checkTimeHash()
</p>
</body>
</html></richcontent>
</node>
<node CREATED="1698108616823" ID="ID_1997400452" LINK="https://stackoverflow.com/a/31488147" MODIFIED="1698108660508" TEXT="baut auf einer ziemlich performanten Bit-Rotation auf, um die halbe Bitbreite">
<icon BUILTIN="info"/>
</node>
</node>
<node COLOR="#338800" CREATED="1698108669351" ID="ID_169046299" MODIFIED="1698110846788" TEXT="Test: ordentiliche Verteilung pr&#xfc;fen">
<icon BUILTIN="button_ok"/>
<node CREATED="1698110522342" ID="ID_207771533" MODIFIED="1698110534252" TEXT="eine Million Samples mitteln"/>
<node CREATED="1698110535960" ID="ID_1180348868" MODIFIED="1698110577238" TEXT="&#x2205; &#x2248; 9995">
<node CREATED="1698110588129" ID="ID_1282443355" MODIFIED="1698110605314" TEXT="SLEEP_HORIZON / 2 w&#xe4;re 10000"/>
<node CREATED="1698110607822" ID="ID_1405325608" MODIFIED="1698110641189" TEXT="4&#x2031; Fehler"/>
</node>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1698110646465" ID="ID_22372803" MODIFIED="1698110702714" TEXT="Abweichung ist weitgehend konstant">
<icon BUILTIN="broken-line"/>
<node CREATED="1698110713872" ID="ID_1461865381" MODIFIED="1698110743424" TEXT="das hei&#xdf;t: es gibt einen systematischen Effekt"/>
<node CREATED="1698110744132" ID="ID_556718826" MODIFIED="1698110762190" TEXT="wenngleich auch sehr klein"/>
</node>
</node>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1698110909230" ID="ID_1719824145" MODIFIED="1698110962018" TEXT="Ausf&#xfc;hren der Verz&#xf6;gerung gem&#xe4;&#xdf; Schema">
<arrowlink COLOR="#6391ac" DESTINATION="ID_664525403" ENDARROW="Default" ENDINCLINATION="-10;127;" ID="Arrow_ID_225157022" STARTARROW="None" STARTINCLINATION="118;-6;"/>
<icon BUILTIN="flag-yellow"/>
</node>
</node>
</node>
@ -90484,6 +90525,9 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</node>
<node BACKGROUND_COLOR="#d2beaf" COLOR="#5c4d6e" CREATED="1698097719743" ID="ID_102946038" MODIFIED="1698097741666" TEXT="G&#xfc;te der Randomisierung per Systemzeit pr&#xfc;fen">
<icon BUILTIN="messagebox_warning"/>
<node COLOR="#435e98" CREATED="1698110801074" ID="ID_1268941360" LINK="#ID_22372803" MODIFIED="1698110840303" TEXT="im SchedulerLoadControl_test nachgemessen">
<icon BUILTIN="idea"/>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1697981156144" ID="ID_190534528" MODIFIED="1697981425435" TEXT="Datenerfassung &#xfc;ber EngineObserver realisieren">