diff --git a/src/vault/gear/activity-lang.hpp b/src/vault/gear/activity-lang.hpp index 9506dc64f..f9c8c9748 100644 --- a/src/vault/gear/activity-lang.hpp +++ b/src/vault/gear/activity-lang.hpp @@ -54,6 +54,7 @@ namespace gear { // using util::isnil; // using std::string; + using BlockFlowAlloc = BlockFlow; /** @@ -64,11 +65,11 @@ namespace gear { */ class ActivityLang { - BlockFlow& mem_; + BlockFlowAlloc& mem_; public: // explicit - ActivityLang (BlockFlow& memManager) + ActivityLang (BlockFlowAlloc& memManager) : mem_{memManager} { } diff --git a/src/vault/gear/block-flow.hpp b/src/vault/gear/block-flow.hpp index 4a0f04991..38e3b2bb6 100644 --- a/src/vault/gear/block-flow.hpp +++ b/src/vault/gear/block-flow.hpp @@ -132,7 +132,7 @@ namespace gear { struct DefaultConfig { /* === characteristic parameters === */ - const size_t EPOCH_SIZ = 100; ///< Number of storage slots to fit into one »Epoch« + const static size_t EPOCH_SIZ = 100; ///< Number of storage slots to fit into one »Epoch« const Duration DUTY_CYCLE{FSecs(1)}; ///< typical relaxation time or average pre-roll to deadline const size_t INITIAL_STREAMS = 2; ///< Number of streams with TYPICAL_FPS to expect for normal use @@ -153,125 +153,183 @@ namespace gear { struct RenderConfig : DefaultConfig { - + const static size_t EPOCH_SIZ = 300; + const size_t INITIAL_STREAMS = 4; }; + /** + * Policy template to mix into the BlockFlow allocator, + * providing the parametrisation for self-regulation + */ template struct Strategy - : CONF { - + CONF const& + config() const + { // Meyers Singleton + static const CONF configInstance; + return configInstance; + } + + size_t + framesPerEpoch() const + { + return config().EPOCH_SIZ / config().ACTIVITIES_PER_FRAME; + } + + const FrameRate + initialFrameRate() const + { + return config().INITIAL_STREAMS * config().TYPICAL_FPS; + } + + Duration + initialEpochStep() const + { + return framesPerEpoch() / initialFrameRate(); + } + + size_t + initialEpochCnt() const ///< reserve allocation headroom for two duty cycles + { + return 1 + 2*_raw(config().DUTY_CYCLE) / _raw(initialEpochStep()); + } + + double + boostFactor() const + { + return config().BOOST_FACTOR; + } + + double + boostFactorOverflow() const ///< reduced logarithmically, since overflow is detected on individual allocations + { + return pow(config().BOOST_FACTOR, 5.0/config().EPOCH_SIZ); + } + + Duration + timeStep_cutOff() const ///< prevent stalling Epoch progression when reaching saturation + { + return _raw(initialEpochStep()) / config().OVERLOAD_LIMIT; + } }; - } + + + + /** + * Allocation Extent holding _scheduler Activities_ to be performed altogether + * before a common _deadline._ Other than the underlying raw Extent, the Epoch + * maintains a deadline time and keeps track of storage slots already claimed. + * This is achieved by using the Activity record in the first slot as a GATE term + * to maintain those administrative information. + * @remark rationale is to discard the Extent as a whole, once deadline passed. + */ + template + class Epoch + : public ALO::Extent + { + using RawIter = typename ALO::iterator; + using SIZ = typename ALO::Extent::SIZ; + + /// @warning will be faked, never constructed + Epoch() = delete; + + public: + /** + * specifically rigged GATE Activity, + * used for managing Epoch metadata + * - the Condition::rest tracks pending async IO operations + * - the Condition::deadline is the nominal deadline of this Epoch + * - the field `next` points to the next free allocation Slot to use + */ + struct EpochGate + : Activity + { + /** @note initially by default there is... + * - effectively no deadline + * - no IO operations pending (i.e. we can just discard the Epoch) + * - the `next` usable Slot is the last Storage slot, and will be + * decremented until there is only one slot left (EpochGate itself) + * @warning EpochGate is assumed to sit in the Epoch's first slot + */ + EpochGate() + : Activity{int(0), Time::ANYTIME} + { + // initialise allocation usage marker: start at last usable slot + next = this + (Epoch::SIZ() - 1); + ENSURE (next != this); + } + // default copyable + + Instant& + deadline() + { + return data_.condition.dead; + } + + bool + isAlive (Time deadline) + { + /////////////////////////////////////////////OOO preliminary implementation ... should use the GATE-Activity itself + return this->deadline() > deadline; + } + + size_t + filledSlots() const + { + const Activity* firstAllocPoint{this + (Epoch::SIZ()-1)}; + return firstAllocPoint - next; + } + + bool + hasFreeSlot() const + { // see C++ § 5.9 : comparison of pointers within same array + return next > this; + } + + Activity* + claimNextSlot() + { + REQUIRE (hasFreeSlot()); + return next--; + } + }; + + + EpochGate& gate() { return static_cast ((*this)[0]); } + Time deadline() { return Time{gate().deadline()}; } + + double + getFillFactor() + { + return double(gate().filledSlots()) / (SIZ()-1); + } + + + static Epoch& + implantInto (RawIter storageSlot) + { + Epoch& target = static_cast (*storageSlot); + new(&target[0]) EpochGate{}; + return target; + } + + static Epoch& + setup (RawIter storageSlot, Time deadline) + { + Epoch& newEpoch{implantInto (storageSlot)}; + newEpoch.gate().deadline() = deadline; + return newEpoch; + } + }; - + }//(End)namespace blockFlow + + template + class FlowDiagnostic; - /** - * Allocation Extent holding _scheduler Activities_ to be performed altogether - * before a common _deadline._ Other than the underlying raw Extent, the Epoch - * maintains a deadline time and keeps track of storage slots already claimed. - * This is achieved by using the Activity record in the first slot as a GATE term - * to maintain those administrative information. - * @remark rationale is to discard the Extent as a whole, once deadline passed. - */ - class Epoch - : public Allocator::Extent - { - - /// @warning will be faked, never constructed - Epoch() = delete; - - public: - /** - * specifically rigged GATE Activity, - * used for managing Epoch metadata - * - the Condition::rest tracks pending async IO operations - * - the Condition::deadline is the nominal deadline of this Epoch - * - the field `next` points to the next free allocation Slot to use - */ - struct EpochGate - : Activity - { - /** @note initially by default there is... - * - effectively no deadline - * - no IO operations pending (i.e. we can just discard the Epoch) - * - the `next` usable Slot is the last Storage slot, and will be - * decremented until there is only one slot left (EpochGate itself) - * @warning EpochGate is assumed to sit in the Epoch's first slot - */ - EpochGate() - : Activity{int(0), Time::ANYTIME} - { - // initialise allocation usage marker: start at last usable slot - next = this + (Epoch::SIZ() - 1); - ENSURE (next != this); - } - // default copyable - - Instant& - deadline() - { - return data_.condition.dead; - } - - bool - isAlive (Time deadline) - { - /////////////////////////////////////////////OOO preliminary implementation ... should use the GATE-Activity itself - return this->deadline() > deadline; - } - - size_t - filledSlots() const - { - const Activity* firstAllocPoint{this + (Epoch::SIZ()-1)}; - return firstAllocPoint - next; - } - - bool - hasFreeSlot() const - { // see C++ § 5.9 : comparison of pointers within same array - return next > this; - } - - Activity* - claimNextSlot() - { - REQUIRE (hasFreeSlot()); - return next--; - } - }; - - - EpochGate& gate() { return static_cast ((*this)[0]); } - Time deadline() { return Time{gate().deadline()}; } - - double - getFillFactor() - { - return double(gate().filledSlots()) / (SIZ()-1); - } - - - static Epoch& - implantInto (Allocator::iterator storageSlot) - { - Epoch& target = static_cast (*storageSlot); - new(&target[0]) EpochGate{}; - return target; - } - - static Epoch& - setup (Allocator::iterator storageSlot, Time deadline) - { - Epoch& newEpoch{implantInto (storageSlot)}; - newEpoch.gate().deadline() = deadline; - return newEpoch; - } - - }; @@ -283,25 +341,37 @@ namespace gear { * @see SchedulerCommutator * @see BlockFlow_test */ + template class BlockFlow - : util::NonCopyable + : public blockFlow::Strategy + , util::NonCopyable { + constexpr static size_t EPOCH_SIZ = CONF::EPOCH_SIZ; + + public: + using Allocator = mem::ExtentFamily; + using RawIter = typename Allocator::iterator; + using Extent = typename Allocator::Extent; + using Epoch = blockFlow::Epoch; + + + private: Allocator alloc_; TimeVar epochStep_; /** @internal use a raw storage Extent as Epoch (unchecked cast) */ static Epoch& - asEpoch (Allocator::Extent& extent) + asEpoch (Extent& extent) { return static_cast (extent); } - struct StorageAdaptor : Allocator::iterator + struct StorageAdaptor : RawIter { StorageAdaptor() = default; - StorageAdaptor(Allocator::iterator it) : Allocator::iterator{it} { } - Epoch& yield() const { return asEpoch (Allocator::iterator::yield()); } + StorageAdaptor(RawIter it) : RawIter{it} { } + Epoch& yield() const { return asEpoch (RawIter::yield()); } }; @@ -347,7 +417,7 @@ namespace gear { BlockFlow* flow_; public: - AllocatorHandle(Allocator::iterator slot, BlockFlow* parent) + AllocatorHandle(RawIter slot, BlockFlow* parent) : epoch_{slot} , flow_{parent} { } @@ -570,7 +640,7 @@ namespace gear { /// „backdoor“ to watch internals from tests - friend class FlowDiagnostic; + friend class FlowDiagnostic; }; @@ -582,12 +652,15 @@ namespace gear { /* ===== Test / Diagnostic ===== */ + template class FlowDiagnostic { - BlockFlow& flow_; + using Epoch = typename BlockFlow::Epoch; + + BlockFlow& flow_; public: - FlowDiagnostic(BlockFlow& theFlow) + FlowDiagnostic(BlockFlow& theFlow) : flow_{theFlow} { } @@ -618,8 +691,9 @@ namespace gear { } }; - inline FlowDiagnostic - watch (BlockFlow& theFlow) + template + inline FlowDiagnostic + watch (BlockFlow& theFlow) { return FlowDiagnostic{theFlow}; } diff --git a/tests/vault/gear/block-flow-test.cpp b/tests/vault/gear/block-flow-test.cpp index 19077cb80..6ac474973 100644 --- a/tests/vault/gear/block-flow-test.cpp +++ b/tests/vault/gear/block-flow-test.cpp @@ -57,6 +57,13 @@ namespace test { // using lib::time::FrameRate; // using lib::time::Time; + namespace { + using BlockFlow = gear::BlockFlow<>; + using Extent = BlockFlow::Extent; + using Epoch = BlockFlow::Epoch; + + const size_t EXTENT_SIZ = Extent::SIZ(); + } @@ -117,7 +124,6 @@ namespace test { void handleEpoch() { - using Extent = Allocator::Extent; // the raw storage Extent is a compact block // providing uninitialised storage typed as `vault::gear::Activity` @@ -172,7 +178,7 @@ namespace test { CHECK (1 == gate.filledSlots()); CHECK (gate.hasFreeSlot()); - CHECK (epoch.getFillFactor() == double(gate.filledSlots()) / (Epoch::SIZ()-1)); + CHECK (epoch.getFillFactor() == double(gate.filledSlots()) / (EXTENT_SIZ-1)); // so let's eat this space up... for (uint i=extent.size()-2; i>1; --i) @@ -180,14 +186,14 @@ namespace test { // one final slot is left (beyond of the EpochGate itself) CHECK (isSameObject (*gate.next, epoch[1])); - CHECK (gate.filledSlots() == Epoch::SIZ()-2); + CHECK (gate.filledSlots() == EXTENT_SIZ-2); CHECK (gate.hasFreeSlot()); gate.claimNextSlot(); // aaand the boat is full... CHECK (not gate.hasFreeSlot()); CHECK (isSameObject (*gate.next, epoch[0])); - CHECK (gate.filledSlots() == Epoch::SIZ()-1); + CHECK (gate.filledSlots() == EXTENT_SIZ-1); CHECK (epoch.getFillFactor() == 1); // a given Epoch can be checked for relevance against a deadline @@ -245,7 +251,7 @@ namespace test { // provoke Epoch overflow by exhausting all available storage slots BlockFlow::AllocatorHandle allocHandle = bFlow.until(Time{300,10}); - for (uint i=1; i - - - + + + + @@ -80245,10 +80246,10 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + - + @@ -80258,8 +80259,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + @@ -80303,29 +80304,28 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + +
- - - - + + + - - + + - - + + - - + + @@ -80867,6 +80867,17 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + + +