diff --git a/src/lib/time/time.cpp b/src/lib/time/time.cpp index 303bc1ad5..96f3c5c3c 100644 --- a/src/lib/time/time.cpp +++ b/src/lib/time/time.cpp @@ -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_( diff --git a/src/lib/time/timevalue.hpp b/src/lib/time/timevalue.hpp index 08d3d96a6..6de086b50 100644 --- a/src/lib/time/timevalue.hpp +++ b/src/lib/time/timevalue.hpp @@ -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) const; + Offset stretchedByFloatFactor (double) const; /** @internal diagnostics, indicating ∆ */ operator std::string () const; @@ -422,9 +423,9 @@ namespace time { return Offset(distance); } - template + template 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(factor.numerator(), factor.denominator())); } + inline Offset + operator* (double factor, Offset const& offset) + { + return offset.stretchedByFloatFactor (factor); + } + /** flip offset direction */ inline Offset diff --git a/src/vault/gear/block-flow.hpp b/src/vault/gear/block-flow.hpp index d6a0eff15..33ecabe52 100644 --- a/src/vault/gear/block-flow.hpp +++ b/src/vault/gear/block-flow.hpp @@ -88,6 +88,7 @@ #include "lib/rational.hpp" #include "lib/nocopy.hpp" #include "lib/util.hpp" +#include "lib/format-cout.hpp"///////////////////////TODO #include @@ -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 ((*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 ⟶ "<alloc_).active()<<" of "<alloc_).size()<getEpochStep()); } @@ -360,9 +367,11 @@ namespace gear { AllocatorHandle until (Time deadline) { +cout<<"||>| "<= deadline) // way into the past ... put it in the first available Epoch +{ +cout<<" ««······ ⟶ ⟶ "< 0) ++requiredNew; // fractional: requested deadline lies within last epoch +cout<<" »»······ "<deadline() >= deadline) +{ +cout<<" ◆◆······ ⟶ "<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_)< 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="< want "<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 - + @@ -80422,11 +80422,14 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + - + + + + @@ -80449,6 +80452,170 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + +

+ die erste belegte Epoche ist exterm viel zu lang ⟹ sehr viele Overflows zu Beginn +

+ +
+ +
+ + + + + + +

+ sie wird für jedes Element aktiviert! +

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

+ vermutlich findet deshalb auch überhaupt kein clean-up statt +

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

+ bisher werden stark unterfüllte Epochen als neutral eingebucht +

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

+ Grundsätzlich ist der Ansatz schon richtig: wenn wir uns weit vom Regelfokus entfernen, dann die Rückstellkraft stärker dämpfen +

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

+ muß die Dämpfung für extreme Zustände so einstellen, +

+

+ daß sie etwa eine Größenordnung langsamer greift, +

+

+ als die charakteristische Regelzeit (≙ Verzögerung) +

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

+ Feinabstimmung: den Testfall rund laufen lasssen +

+ +
+ + + + + + + +

+ Die Korrektur bei Overflows darf nur so stark sein, daß die Blöcke während der Vorlaufzeit etwa halbiert werden, aber nicht so stark, daß wir gegen das Limit laufen +

+ +
+
+ + + + + + +
    +
  • + wenn die Blöcke noch leer sind, kommt es zu Regelschwingungen und es dauert doppelt so lange bis zum stationären Zustand +
  • +
  • + weitgehend volle Blöcke dagegen verlängern die Überlastungsphase unnötig +
  • +
+ +
+
+ + +