Block-Flow: investigate, fix and fine-tune Epoch size control

- BUG: must prevent the Epoch size to become excessive low
- Problem: feedback signal should not be overly aggressive

Fine-Tuning:
- Dose for Overflow-compensation is delicate
- Moving average and Overflow should be balanced
- ideally the compensatory actions should be one order of magnitude
  slower than the characteristic regulation time

Improvement: perform Moving-Average calculations in doubles
This commit is contained in:
Fischlurch 2023-07-18 21:23:00 +02:00
parent c7d6f3e24c
commit c008858d8f
5 changed files with 278 additions and 48 deletions

View file

@ -277,6 +277,16 @@ namespace time {
}
Offset
Offset::stretchedByFloatFactor (double factor) const
{
double distance(this->t_);
distance *= factor;
gavl_time_t microTicks = floor (distance);
return Offset{buildRaw_(microTicks)};
}
/** offset by the given number of frames. */
Offset::Offset (FrameCnt count, FrameRate const& fps)
: TimeValue{buildRaw_(

View file

@ -396,6 +396,7 @@ namespace time {
/** @internal stretch offset by a possibly fractional factor,
* and quantise into raw (micro tick) grid */
Offset stretchedByRationalFactor (boost::rational<int64_t>) const;
Offset stretchedByFloatFactor (double) const;
/** @internal diagnostics, indicating ∆ */
operator std::string () const;
@ -422,9 +423,9 @@ namespace time {
return Offset(distance);
}
template<typename INT>
template<typename FAC>
inline Offset
operator* (Offset const& distance, INT factor)
operator* (Offset const& distance, FAC factor)
{
return factor*distance;
}
@ -445,6 +446,12 @@ namespace time {
return offset.stretchedByRationalFactor (boost::rational<int64_t>(factor.numerator(), factor.denominator()));
}
inline Offset
operator* (double factor, Offset const& offset)
{
return offset.stretchedByFloatFactor (factor);
}
/** flip offset direction */
inline Offset

View file

@ -88,6 +88,7 @@
#include "lib/rational.hpp"
#include "lib/nocopy.hpp"
#include "lib/util.hpp"
#include "lib/format-cout.hpp"///////////////////////TODO
#include <utility>
@ -113,8 +114,10 @@ namespace gear {
const Duration INITIAL_EPOCH_STEP{FRAMES_PER_EPOCH * FrameRate{50}.duration()};
const Rat OVERFLOW_BOOST_FACTOR = 9_r/10; ///< increase capacity on each Epoch overflow event
const Rat EMPTY_THRESHOLD = 1_r/20; ///< do not account for (almost) empty Epochs to avoid overshooting regulation
const double BOOST_FACTOR = 0.85; ///< adjust capacity by this factor on Epoch overflow/underflow events
const double BOOST_OVERFLOW = pow(BOOST_FACTOR, 1.0/EPOCH_SIZ);
const double DAMP_THRESHOLD = 0.06; ///< do not account for (almost) empty Epochs to avoid overshooting regulation
const TimeValue MIN_EPOCH_STEP{1000}; ///< minimal Epoch spacing in µs to prevent stalled Epoch progression
const size_t AVERAGE_EPOCHS = 10; ///< moving average len for exponential convergence towards average Epoch fill
/** raw allocator to provide a sequence of Extents to place Activity records */
@ -204,10 +207,10 @@ namespace gear {
EpochGate& gate() { return static_cast<EpochGate&> ((*this)[0]); }
Time deadline() { return Time{gate().deadline()}; }
Rat
double
getFillFactor()
{
return Rat{gate().filledSlots(), SIZ()-1};
return double(gate().filledSlots()) / (SIZ()-1);
}
@ -275,9 +278,12 @@ namespace gear {
}
void
adjustEpochStep (Rat factor)
adjustEpochStep (double factor)
{
epochStep_ = getEpochStep() * factor;
double stretched = _raw(epochStep_) * factor;
gavl_time_t microTicks(floor (stretched));
epochStep_ = TimeValue{microTicks};
}
@ -338,6 +344,7 @@ namespace gear {
{
auto lastDeadline = flow_->lastEpoch().deadline();
epoch_.expandAlloc(); // may throw out-of-memory..
cout<<"||∧| +1 ⟶ "<<watch(flow_->alloc_).active()<<" of "<<watch(flow_->alloc_).size()<<endl;
ENSURE (epoch_);
Epoch::setup (epoch_, lastDeadline + flow_->getEpochStep());
}
@ -360,9 +367,11 @@ namespace gear {
AllocatorHandle
until (Time deadline)
{
cout<<"||>| "<<TimeValue(deadline);
if (isnil (alloc_))
{//just create new Epoch one epochStep ahead
alloc_.openNew();
cout<<"\n||∧| +1 ⟶ "<<watch(alloc_).active()<<" of "<<watch(alloc_).size()<<endl;
Epoch::setup (alloc_.begin(), deadline + Time{epochStep_});
return AllocatorHandle{alloc_.begin(), this};
}
@ -370,7 +379,10 @@ namespace gear {
{//find out how the given time relates to existing Epochs
if (firstEpoch().deadline() >= deadline)
// way into the past ... put it in the first available Epoch
{
cout<<" ««······ ⟶ ⟶ "<<TimeValue{firstEpoch().deadline()}<<endl;
return AllocatorHandle{alloc_.begin(), this};
}
else
if (lastEpoch().deadline() < deadline)
{ // a deadline beyond the established Epochs...
@ -382,7 +394,9 @@ namespace gear {
auto requiredNew = distance / _raw(epochStep_);
if (distance % _raw(epochStep_) > 0)
++requiredNew; // fractional: requested deadline lies within last epoch
cout<<" »»······ "<<lastDeadline<<" ⟶ Δ"<<TimeValue(distance)<<endl;
alloc_.openNew(requiredNew); // Note: epochHandle now points to the first new Epoch
cout<<"||∧| +"<<requiredNew<<""<<watch(alloc_).active()<<" of "<<watch(alloc_).size()<<endl;
for ( ; 0 < requiredNew; --requiredNew)
{
REQUIRE (nextEpoch);
@ -400,7 +414,10 @@ namespace gear {
else
for (EpochIter epochIt{alloc_.begin()}; epochIt; ++epochIt)
if (epochIt->deadline() >= deadline)
{
cout<<" ◆◆······ ⟶ "<<TimeValue{epochIt->deadline()}<<endl;
return AllocatorHandle{epochIt, this};
}
NOTREACHED ("Inconsistency in BlockFlow Epoch deadline organisation");
}
@ -416,6 +433,7 @@ namespace gear {
void
discardBefore (Time deadline)
{
cout<<"||■| CLUP <- "<<TimeValue(deadline)<<endl;
if (isnil (alloc_)
or firstEpoch().deadline() > deadline)
return;
@ -444,7 +462,9 @@ namespace gear {
void
markEpochOverflow()
{
adjustEpochStep (OVERFLOW_BOOST_FACTOR);
if (epochStep_ > MIN_EPOCH_STEP)
adjustEpochStep (BOOST_OVERFLOW);
cout<<"||*| OVER -> "<<_raw(epochStep_)<<endl;
}
/**
@ -462,15 +482,23 @@ namespace gear {
* without much spurious range checks. /////////////////////////////////////////////////////////TICKET #1259 : reorganise raw time base datatypes : need conversion path into FSecs
*/
void
markEpochUnderflow (TimeVar actualLen, Rat fillFactor)
markEpochUnderflow (TimeVar actualLen, double fillFactor)
{
Rat contribution{_raw(actualLen), _raw(epochStep_)};
if (fillFactor > EMPTY_THRESHOLD) // treat almost empty blocks as if their length was optimal
contribution /= fillFactor;
auto interpolate = [&](auto f, auto v1, auto v2) { return f*v2 + (1-f)*v1; };
// use actual fill as signal, but limit signal for empty Epochs
double adjust =
fillFactor > DAMP_THRESHOLD? fillFactor
: interpolate (DAMP_THRESHOLD-fillFactor, fillFactor,BOOST_FACTOR);
// damped adjustment towards ideal size
double contribution = double(_raw(actualLen)) / _raw(epochStep_) / adjust;
// Exponential MA: mean ≔ mean * (N-1)/N + newVal/N
const Rat N = AVERAGE_EPOCHS;
Rat avgFactor = (contribution + N-1) / N;
auto N = AVERAGE_EPOCHS;
double avgFactor = (contribution + N-1) / N;
adjustEpochStep (avgFactor);
cout<<"||| MAVG -> "<<_raw(epochStep_)<<" <= len="<<actualLen<<" fill="<<fillFactor<<" -> want "<<TimeValue{gavl_time_t(_raw(actualLen)/fillFactor)}<<" con:"<<contribution<< " * "<<avgFactor<<endl;
}

View file

@ -172,7 +172,7 @@ namespace test {
CHECK (1 == gate.filledSlots());
CHECK (gate.hasFreeSlot());
CHECK (epoch.getFillFactor() == Rat(gate.filledSlots(), Epoch::SIZ()-1));
CHECK (epoch.getFillFactor() == double(gate.filledSlots()) / (Epoch::SIZ()-1));
// so let's eat this space up...
for (uint i=extent.size()-2; i>1; --i)
@ -278,22 +278,23 @@ namespace test {
CHECK (not allocHandle.hasFreeSlot());
auto& a6 = bFlow.until(Time{850,10}).create();
// Note: encountered four overflow-Events, leading to decreased Epoch spacing for new Epochs
CHECK (watch(bFlow).find(a6) == "11s131ms"_expect);
CHECK (watch(bFlow).allEpochs() == "10s200ms|10s400ms|10s600ms|10s800ms|11s|11s131ms"_expect);
CHECK (watch(bFlow).find(a6) == "11s198ms"_expect);
CHECK (watch(bFlow).allEpochs() == "10s200ms|10s400ms|10s600ms|10s800ms|11s|11s198ms"_expect);
auto& a7 = bFlow.until(Time{500,11}).create();
// this allocation does not count as overflow, but has to expand the Epoch grid, now using the reduced Epoch spacing
CHECK (watch(bFlow).allEpochs() == "10s200ms|10s400ms|10s600ms|10s800ms|11s|11s131ms|11s262ms|11s393ms|11s524ms"_expect);
CHECK (watch(bFlow).find(a7) == "11s524ms"_expect);
CHECK (watch(bFlow).allEpochs() == "10s200ms|10s400ms|10s600ms|10s800ms|11s|11s198ms|11s397ms|11s596ms"_expect);
CHECK (watch(bFlow).find(a7) == "11s596ms"_expect);
CHECK (bFlow.getEpochStep() == "≺131ms≻"_expect);
// on clean-up, actual fill ratio is used to adjust to optimise Epoch length for better space usage
CHECK (bFlow.getEpochStep() == "≺198ms≻"_expect);
bFlow.discardBefore (Time{999,10});
CHECK (bFlow.getEpochStep() == "149ms≻"_expect);
CHECK (watch(bFlow).allEpochs() == "11s|11s131ms|11s262ms|11s393ms|11s524ms"_expect);
CHECK (bFlow.getEpochStep() == "802ms≻"_expect);
CHECK (watch(bFlow).allEpochs() == "11s|11s198ms|11s397ms|11s596ms"_expect);
// placed into the oldest Epoch still alive
auto& a8 = bFlow.until(Time{500,10}).create();
CHECK (watch(bFlow).find(a8) == "11s131ms"_expect);
CHECK (watch(bFlow).find(a8) == "11s198ms"_expect);
}
@ -311,31 +312,45 @@ namespace test {
CHECK (bFlow.getEpochStep() == INITIAL_EPOCH_STEP);
// whenever an Epoch overflow happens, capacity is boosted by reducing the Epoch duration
SHOW_EXPR(bFlow.getEpochStep())
bFlow.markEpochOverflow();
CHECK (bFlow.getEpochStep() == INITIAL_EPOCH_STEP * OVERFLOW_BOOST_FACTOR);
SHOW_EXPR(bFlow.getEpochStep())
SHOW_EXPR(INITIAL_EPOCH_STEP)
SHOW_EXPR(INITIAL_EPOCH_STEP*BOOST_OVERFLOW)
CHECK (bFlow.getEpochStep() == INITIAL_EPOCH_STEP * BOOST_OVERFLOW);
bFlow.markEpochOverflow();
CHECK (bFlow.getEpochStep() == INITIAL_EPOCH_STEP * OVERFLOW_BOOST_FACTOR*OVERFLOW_BOOST_FACTOR);
CHECK (bFlow.getEpochStep() == INITIAL_EPOCH_STEP * BOOST_OVERFLOW*BOOST_OVERFLOW);
// To counteract this increase, on clean-up the actual fill rate of the Extent
// serves to guess an optimal Epoch duration, which is averaged exponentially
// Using just arbitrary demo values for some fictional Epochs
TimeVar dur1 = INITIAL_EPOCH_STEP;
Rat fill1 = 8_r/10;
TimeVar dur2 = INITIAL_EPOCH_STEP * OVERFLOW_BOOST_FACTOR;
Rat fill2 = 3_r/10;
double fac1 = 0.8;
TimeVar dur2 = INITIAL_EPOCH_STEP * BOOST_OVERFLOW;
double fac2 = 0.4;
Rat N = AVERAGE_EPOCHS;
TimeVar step = bFlow.getEpochStep();
auto movingAverage = [&](TimeValue old, double contribution)
{
auto N = AVERAGE_EPOCHS;
auto averageTicks = double(_raw(old))*(N-1)/N + contribution/N;
return TimeValue{gavl_time_t (floor (averageTicks))};
};
bFlow.markEpochUnderflow (dur1, fill1);
CHECK (bFlow.getEpochStep() == Duration{FSecs{step}*(N-1)/N + FSecs{dur1}/fill1/N});
SHOW_EXPR(bFlow.getEpochStep())
bFlow.markEpochUnderflow (dur1, fac1);
SHOW_EXPR(bFlow.getEpochStep())
SHOW_EXPR(movingAverage(step, double(_raw(dur1)) / fac1))
CHECK (bFlow.getEpochStep() == movingAverage(step, double(_raw(dur1)) / fac1));
step = bFlow.getEpochStep();
bFlow.markEpochUnderflow (dur2, fill2);
CHECK (bFlow.getEpochStep() == Duration{FSecs{step}*(N-1)/N + FSecs{dur2}/fill2/N});
} // Note: for verification the exponential average is computed via FSecs
// which is a different calculation path but yields the same result
bFlow.markEpochUnderflow (dur2, fac2);
SHOW_EXPR(_raw(bFlow.getEpochStep()))
SHOW_EXPR(_raw(movingAverage(step, double(_raw(dur2)) / fac2)))
CHECK (bFlow.getEpochStep() == movingAverage(step, double(_raw(dur2)) / fac2));
}
/** @test investigate progression of epochs under realistic load
@ -363,13 +378,14 @@ namespace test {
// pre-generate random test data
TestData testData{INSTANCES};
for (auto&[t,r] : testData)
for (size_t i=0; i<INSTANCES; ++i)
{
const size_t SPREAD = 2*_raw(SPREAD_DEAD);
const size_t MIN_DEAD = _raw(BASE_DEADLINE) - _raw(SPREAD_DEAD);
auto&[t,r] = testData[i];
r = rand() % SPREAD;
t = TimeValue(MIN_DEAD + r);
t = TimeValue(i*STP + MIN_DEAD + r);
}
Activity dummy; // reserve memory for test subject index
@ -458,15 +474,16 @@ namespace test {
size_t check = feedActivity.data_.feed.one;
if (i % CLEAN_UP == 0)
blockFlow.discardBefore (Time{TimeValue{i*STP}});
++i;
return check;
};
sum3 = runTest (allocate, invoke);
//SHOW_EXPR(watch(blockFlow).cntEpochs())
//SHOW_EXPR(watch(blockFlow).poolSize())
//SHOW_EXPR(watch(blockFlow).first())
//SHOW_EXPR(watch(blockFlow).last())
//SHOW_EXPR(_raw(blockFlow.getEpochStep()))
SHOW_EXPR(watch(blockFlow).cntEpochs())
SHOW_EXPR(watch(blockFlow).poolSize())
SHOW_EXPR(watch(blockFlow).first())
SHOW_EXPR(watch(blockFlow).last())
SHOW_EXPR(_raw(blockFlow.getEpochStep()))
//SHOW_EXPR(watch(blockFlow).allEpochs())
};
@ -479,7 +496,8 @@ SHOW_EXPR(sum1);
auto time_heapAlloc = benchmark(heapAlloc);
SHOW_EXPR(time_heapAlloc)
SHOW_EXPR(sum2);
cout<<"\n\n■□■□■□■□■□■□■□■□■□■□■□■□■□■□■□■□■□■□■□■□■□■□■□■□■□■□■□■□■□"<<endl;
// INVOKE Setup-3
auto time_blockFlow = benchmark(blockFlow);
SHOW_EXPR(time_blockFlow)

View file

@ -80396,7 +80396,7 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1689640146728" ID="ID_1549819920" MODIFIED="1689640170406" TEXT="Hinweise auf Blocksteuerung">
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1689640146728" ID="ID_1549819920" MODIFIED="1689684857285" TEXT="Probleme in der Blocksteuerung">
<icon BUILTIN="broken-line"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1689640172272" ID="ID_581706192" MODIFIED="1689640208346" TEXT="Epoch-Step ist zuletzt 0">
<icon BUILTIN="broken-line"/>
@ -80422,11 +80422,14 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1689642371947" ID="ID_1406026816" MODIFIED="1689642405098" TEXT="insgesamt: m&#xf6;glicherweise ist das Konzept so nicht tragf&#xe4;hig">
<icon BUILTIN="messagebox_warning"/>
<node CREATED="1689642408734" ID="ID_1850569145" MODIFIED="1689642449156" TEXT="schon die Parametrisierung f&#xfc;r den Test zeit einen viel zu kleinen Hebel"/>
<node CREATED="1689642450264" ID="ID_1125214093" MODIFIED="1689642537977" TEXT="es ist klar: die beiden anderen Varianten sind nicht skalierbar">
<node CREATED="1689642408734" ID="ID_1850569145" MODIFIED="1689642449156" TEXT="schon die Parametrisierung f&#xfc;r den Test zeigt einen viel zu kleinen Hebel"/>
<node CREATED="1689642450264" ID="ID_1125214093" MODIFIED="1689684699740" TEXT="allerdings ist klar: die beiden anderen Varianten sind nicht skalierbar">
<node CREATED="1689642540378" ID="ID_1216826442" MODIFIED="1689642547999" TEXT="wir brauchen Dauerbetrieb"/>
<node CREATED="1689642549891" ID="ID_960993571" MODIFIED="1689642558846" TEXT="Heap fragmentiert im Lauf der Zeit"/>
<node CREATED="1689642582295" ID="ID_1154028289" MODIFIED="1689642651289" TEXT="wir brauchen sichere Erkennung freiggebarer Records"/>
<node BACKGROUND_COLOR="#ccb59b" COLOR="#6e2a38" CREATED="1689642582295" ID="ID_1154028289" MODIFIED="1689684715594" TEXT="wir brauchen sichere Erkennung freiggebarer Records">
<font ITALIC="true" NAME="SansSerif" SIZE="14"/>
<icon BUILTIN="yes"/>
</node>
</node>
<node CREATED="1689642751072" ID="ID_1608372510" MODIFIED="1689642786257">
<richcontent TYPE="NODE"><html>
@ -80449,6 +80452,170 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1689684827456" ID="ID_1251456862" MODIFIED="1689684834449" TEXT="Untersuchung">
<icon BUILTIN="pencil"/>
<node COLOR="#338800" CREATED="1689688605214" ID="ID_1479889724" MODIFIED="1689688613864" TEXT="Instrumentierung per Dump-Message">
<icon BUILTIN="button_ok"/>
</node>
<node CREATED="1689688665057" ID="ID_191179239" MODIFIED="1689689020435">
<richcontent TYPE="NODE"><html>
<head/>
<body>
<p>
die erste belegte Epoche ist <i>exterm viel zu lang</i>&#160;&#10233; sehr <b>viele Overflows</b>&#160;zu Beginn
</p>
</body>
</html></richcontent>
<icon BUILTIN="messagebox_warning"/>
</node>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1689688809029" ID="ID_197704228" MODIFIED="1689689024928" TEXT="die Overflow-Skalierung ist zu aggresiv">
<icon BUILTIN="messagebox_warning"/>
<node CREATED="1689688867029" ID="ID_370596400" MODIFIED="1689688892793">
<richcontent TYPE="NODE"><html>
<head/>
<body>
<p>
sie wird <i>f&#252;r jedes Element </i>aktiviert!
</p>
</body>
</html></richcontent>
</node>
<node CREATED="1689688913178" ID="ID_750544240" MODIFIED="1689688932088" TEXT="...ist aber konzeptionell auf Block-Ebene gedacht"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1689688962913" ID="ID_1246079128" MODIFIED="1689688986512" TEXT="abschw&#xe4;chen auf Wurzel-Blockgr&#xf6;&#xdf;e">
<icon BUILTIN="flag-yellow"/>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1689688991172" ID="ID_1205551912" MODIFIED="1689689193276" TEXT="es fehlt eine Mindestgrenze">
<icon BUILTIN="broken-line"/>
<node CREATED="1689689101381" ID="ID_1069284022" MODIFIED="1689689142544" TEXT="damit l&#xe4;uft der Wert irgendwann in die Aufl&#xf6;sungsgrenze">
<icon BUILTIN="broken-line"/>
</node>
<node CREATED="1689689121643" ID="ID_1139921351" MODIFIED="1689689145605" TEXT="zumal sehr viel h&#xe4;ufiger / aggressiver skaliert wird (als intendiert)">
<icon BUILTIN="idea"/>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1689689147911" ID="ID_325871925" MODIFIED="1689689193276" TEXT="die generierten Deadlines vom Test sind falsch">
<icon BUILTIN="broken-line"/>
<node CREATED="1689689201144" ID="ID_1519775847" MODIFIED="1689689215381" TEXT="damit kommt das Epochen-System nicht vom Fleck"/>
<node CREATED="1689689279445" ID="ID_1830236844" MODIFIED="1689689306124">
<richcontent TYPE="NODE"><html>
<head/>
<body>
<p>
vermutlich findet deshalb auch <b>&#252;berhaupt kein clean-up</b>&#160;statt
</p>
</body>
</html></richcontent>
</node>
<node CREATED="1689689307341" ID="ID_1812665874" MODIFIED="1689689327476" TEXT="weshalb dann auch die division-by-zero gar nicht getriggert wird"/>
</node>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1689692582553" ID="ID_291005420" MODIFIED="1689692625099" TEXT="die Bremse im MovingAverage ist kontraproduktiv">
<icon BUILTIN="messagebox_warning"/>
<node CREATED="1689692626589" ID="ID_629425172" MODIFIED="1689692652763">
<richcontent TYPE="NODE"><html>
<head/>
<body>
<p>
bisher werden stark unterf&#252;llte Epochen als <i>neutral </i>eingebucht
</p>
</body>
</html></richcontent>
</node>
<node CREATED="1689692673756" ID="ID_397401907" MODIFIED="1689692700628" TEXT="das f&#xfc;hrt dazu, da&#xdf; sich eine sehr kleine Epochen-L&#xe4;nge &#x201e;festsetzt&#x201c;"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1689692715478" ID="ID_1884642604" MODIFIED="1689692741660" TEXT="stattdessen genau mit dem Gegenst&#xfc;ck zur Overflow-Skalierung hochfahren">
<icon BUILTIN="flag-yellow"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1689695509771" ID="ID_1680463612" MODIFIED="1689695577443" TEXT="besser noch: linear interpolieren zwischen diesem und dem tats&#xe4;chlichen Signal">
<richcontent TYPE="NOTE"><html>
<head/>
<body>
<p>
Grunds&#228;tzlich ist der Ansatz schon richtig: wenn wir uns weit vom Regelfokus entfernen, dann die R&#252;ckstellkraft st&#228;rker d&#228;mpfen
</p>
</body>
</html></richcontent>
<icon BUILTIN="flag-yellow"/>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1689706630682" ID="ID_1207312075" MODIFIED="1689706639238" TEXT="Beobachtungen-2">
<icon BUILTIN="idea"/>
<node COLOR="#338800" CREATED="1689706647127" ID="ID_122402325" MODIFIED="1689706656742" TEXT="nach Beheben der Fehler greift die Regelung">
<icon BUILTIN="button_ok"/>
</node>
<node CREATED="1689706729972" ID="ID_109351292" MODIFIED="1689707548071" TEXT="der Moving-Average mu&#xdf; vor allem die Verz&#xf6;gerung zum Me&#xdf;punkt abfangen">
<icon BUILTIN="idea"/>
</node>
<node CREATED="1689706842221" ID="ID_46527499" MODIFIED="1689706924295">
<richcontent TYPE="NODE"><html>
<head>
</head>
<body>
<p>
mu&#223; die D&#228;mpfung f&#252;r extreme Zust&#228;nde so einstellen,
</p>
<p>
da&#223; sie etwa eine Gr&#246;&#223;enordnung langsamer greift,
</p>
<p>
als die charakteristische Regelzeit (&#8793; Verz&#246;gerung)
</p>
</body>
</html></richcontent>
<icon BUILTIN="idea"/>
</node>
<node CREATED="1689707069750" ID="ID_1676110559" MODIFIED="1689707543214" TEXT="die Overflow-Korrektur sollte im Schnitt etwas st&#xe4;rker wirken">
<icon BUILTIN="idea"/>
<node CREATED="1689707102105" ID="ID_1477862319" MODIFIED="1689707113227" TEXT="gilt nur im stabilen Zustand"/>
<node CREATED="1689707125990" ID="ID_86627735" MODIFIED="1689707145143" TEXT="weil dort Overflows nur punktweise auftreten"/>
<node CREATED="1689707145850" ID="ID_218220838" MODIFIED="1689707160501" TEXT="und Overflows generell die Performance beeintr&#xe4;chtigen"/>
<node CREATED="1689707223025" ID="ID_18144633" MODIFIED="1689707248873" TEXT="die zus&#xe4;tziche D&#xe4;mpfung durch den MovingAverage hat aber bereits genau diesen Effekt"/>
</node>
<node COLOR="#338800" CREATED="1689707264498" ID="ID_36545794" MODIFIED="1689707536374">
<richcontent TYPE="NODE"><html>
<head>
</head>
<body>
<p>
<b>Feinabstimmung</b>: den Testfall rund laufen lasssen
</p>
</body>
</html></richcontent>
<icon BUILTIN="button_ok"/>
<node CREATED="1689707302230" ID="ID_1843953493" MODIFIED="1689707397166" TEXT="Overflow-Kompensation vs. Vorlaufzeit f&#xfc;r MA">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
Die Korrektur bei Overflows darf nur so stark sein, da&#223; die Bl&#246;cke w&#228;hrend der Vorlaufzeit etwa halbiert werden, aber nicht so stark, da&#223; wir gegen das Limit laufen
</p>
</body>
</html></richcontent>
</node>
<node CREATED="1689707398181" ID="ID_536362200" MODIFIED="1689707492490" TEXT="bei Einsetzen der Regelung sind halb-volle Bl&#xf6;cke optimal">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<ul>
<li>
wenn die Bl&#246;cke noch leer sind, kommt es zu Regelschwingungen und es dauert doppelt so lange bis zum station&#228;ren Zustand
</li>
<li>
weitgehend volle Bl&#246;cke dagegen verl&#228;ngern die &#220;berlastungsphase unn&#246;tig
</li>
</ul>
</body>
</html></richcontent>
</node>
<node CREATED="1689707495357" ID="ID_1663505203" MODIFIED="1689707522915" TEXT="&#x27f9; Default-Werte sollten initial ehr auf Unterf&#xfc;llung zielen"/>
</node>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1688559356693" HGAP="9" ID="ID_1823374976" MODIFIED="1688559375797" TEXT="Grenzf&#xe4;lle" VSHIFT="7">