Block-Flow: rationalise iterator usage

...with the preceding IterableDecorator refactoring,
the navigation and access to the storage extents can now be
organised into a clear progression

Allocator::iterator -> EpochIter -> Epoch&

Convenience management and support functions can then be
pushed down into Epoch, while iteration control can be done
high-level in BlockFlow, based on the helpers in Epoch
This commit is contained in:
Fischlurch 2023-07-13 18:35:10 +02:00
parent 5a8463acce
commit 5055ba7144
4 changed files with 125 additions and 67 deletions

View file

@ -54,12 +54,10 @@
#include "vault/common.hpp"
#include "vault/gear/activity.hpp"
#include "vault/mem/extent-family.hpp"
//#include "lib/symbol.hpp"
#include "lib/time/timevalue.hpp"
#include "lib/nocopy.hpp"
#include "lib/util.hpp"
//#include <string>
#include <utility>
@ -67,12 +65,12 @@ namespace vault{
namespace gear {
using util::isnil;
// using std::string;
using lib::time::Time;
using lib::time::TimeVar;
using lib::time::Duration;
using lib::time::FrameRate;
namespace {// hard-wired parametrisation
const size_t EPOCH_SIZ = 100;
const size_t ACTIVITIES_PER_FRAME = 10;
@ -94,13 +92,13 @@ namespace gear {
* 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 the deadline passed.
* @remark rationale is to discard the Extent as a whole, once deadline passed.
*/
class Epoch
: public Allocator::Extent
{
/// @warning will be faked, not constructed
/// @warning will be faked, never constructed
Epoch() = delete;
public:
@ -150,19 +148,27 @@ namespace gear {
}
};
EpochGate& gate() { return static_cast<EpochGate&> ((*this)[0]); }
Time deadline() { return Time{gate().deadline()}; }
static Epoch&
implantInto (Allocator::Extent& rawStorage)
implantInto (Allocator::iterator storageSlot)
{
Epoch& target = static_cast<Epoch&> (rawStorage);
Epoch& target = static_cast<Epoch&> (*storageSlot);
new(&target[0]) EpochGate{};
return target;
}
EpochGate&
gate()
static Epoch&
setup (Allocator::iterator storageSlot, Time deadline)
{
return static_cast<EpochGate&> ((*this)[0]);
Epoch& newEpoch{implantInto (storageSlot)};
newEpoch.gate().deadline() = deadline;
return newEpoch;
}
};
@ -181,6 +187,22 @@ namespace gear {
Allocator alloc_;
TimeVar epochStep_;
/** @internal use a raw storage Extent as Epoch (unchecked cast) */
static Epoch&
asEpoch (Allocator::Extent& extent)
{
return static_cast<Epoch&> (extent);
}
struct StorageAdaptor : Allocator::iterator
{
StorageAdaptor(Allocator::iterator it) : Allocator::iterator{it} { }
Epoch& yield() const { return asEpoch (Allocator::iterator::yield()); }
};
public:
BlockFlow()
: alloc_{INITIAL_ALLOC}
@ -194,6 +216,10 @@ namespace gear {
}
/** Adapted storage-extent iterator, directly exposing Extent& */
using EpochIter = lib::IterableDecorator<Epoch, StorageAdaptor>;
/**
* Local handle to allow allocating a collection of Activities,
* all sharing a common deadline. Internally, these records are
@ -203,13 +229,16 @@ namespace gear {
*/
class AllocatorHandle
{
Allocator::iterator extent;
EpochIter epoch_;
public:
AllocatorHandle(Allocator::iterator slot)
: extent{slot}
: epoch_{slot}
{ }
/*************************************************//**
* Main API operation: allocate a new Activity record
*/
template<typename...ARGS>
Activity&
create (ARGS&& ...args)
@ -218,18 +247,12 @@ namespace gear {
}
private:
Epoch&
currEpoch()
{
return asEpoch(extent);
}
void*
claimSlot() ///< EX_SANE
{
if (currEpoch().gate().hasFreeSlot())
if (epoch_->gate().hasFreeSlot())
{
return currEpoch().gate().claimNextSlot();
return epoch_->gate().claimNextSlot();
}
else // Epoch overflow
{ // use following Epoch; possibly allocate
@ -238,14 +261,16 @@ namespace gear {
}
};
/* ===== public BlockFlow API ===== */
AllocatorHandle
until (Time deadline)
{
if (isnil (alloc_))
{//just create new Epoch one epochStep ahead
alloc_.openNew();
Epoch& newEpoch = Epoch::implantInto (alloc_.first());
newEpoch.gate().deadline() = deadline + Time{epochStep_};
Epoch::setup (alloc_.begin(), deadline + Time{epochStep_});
return AllocatorHandle{alloc_.begin()};
}
else
@ -258,23 +283,24 @@ namespace gear {
discardBefore (Time deadline)
{
if (isnil (alloc_)
or asEpoch(alloc_.first()).gate().deadline() > deadline)
or firstEpoch().deadline() > deadline)
return;
}
private:
/** @internal use a raw allocator Extent as Epoch (unchecked cast) */
static Epoch&
asEpoch (Allocator::iterator slot)
{
REQUIRE (bool(slot));
return asEpoch (*slot);
}
static Epoch&
asEpoch (Allocator::Extent& extent)
private:
Epoch&
firstEpoch()
{
return static_cast<Epoch&> (extent);
REQUIRE (not isnil (alloc_));
return asEpoch(*alloc_.begin());
}
Epoch&
lastEpoch()
{
REQUIRE (not isnil (alloc_));
return asEpoch(*alloc_.last());
}
@ -300,10 +326,10 @@ namespace gear {
: flow_{theFlow}
{ }
Time first() { return Time{BlockFlow::asEpoch(flow_.alloc_.first()).gate().deadline()}; }
Time last() { return Time{BlockFlow::asEpoch(flow_.alloc_.last() ).gate().deadline()}; }
Time first() { return flow_.firstEpoch().deadline();}
Time last() { return flow_.lastEpoch().deadline(); }
size_t cntEpochs() { return watch(flow_.alloc_).active(); }
size_t poolSize() { return watch(flow_.alloc_).size(); }
size_t poolSize() { return watch(flow_.alloc_).size(); }
};
inline FlowDiagnostic

View file

@ -141,7 +141,7 @@ namespace mem {
void
iterNext()
{
exFam->incWrap (index);
index = exFam->incWrap (index);
}
bool
@ -150,6 +150,12 @@ namespace mem {
return exFam == oi.exFam
and index == oi.index;
}
/* === pass-through extended functionality === */
size_t getIndex() { return index; }
void expandAlloc(){ exFam->openNew();}
};
@ -184,7 +190,7 @@ namespace mem {
{
if (not canAccomodate (cnt))
{//insufficient reserve => allocate
size_t oldSiz = extents_.size();
size_t oldSiz = slotCnt();
size_t addSiz = cnt - freeSlotCnt()
+ EXCESS_ALLOC;
// add a strike of new extents at the end
@ -202,7 +208,7 @@ namespace mem {
}
// now sufficient reserve extents are available
ENSURE (canAccomodate (cnt));
incWrap (after_, cnt);
after_ = incWrap (after_, cnt);
}
/** discard oldest \a cnt extents */
@ -210,18 +216,13 @@ namespace mem {
dropOld (size_t cnt)
{
REQUIRE (cnt <= activeSlotCnt());
incWrap (start_, cnt);
start_ = incWrap (start_, cnt);
} ////////////////////////////////////////////////////////////////////////////TICKET #1316 : should reduce excess allocation (with appropriate damping to avoid oscillations)
/** allow transparent iteration of Extents,
* with the ability to expand storage */
struct iterator
: lib::IterStateWrapper<Extent, IdxLink>
{
size_t getIndex() { return this->stateCore().index; }
void expandAlloc(){ this->stateCore().exFam->openNew();}
};
using iterator = lib::IterableDecorator<Extent, IdxLink>;
/** iterate over all the currently active Extents */
iterator begin() { return iterator{IdxLink{this, start_}}; }
@ -232,8 +233,17 @@ namespace mem {
bool empty() const { return start_ == after_; }
Extent& last() const { return access((after_+extents_.size()-1) % extents_.size()); } ///< @warning undefined behaviour when empty
Extent& first() const { return access(start_); } ///< @warning undefined behaviour when empty
/** positioned to the last / latest storage extent opened
* @warning undefined behaviour when empty
*/
iterator
last()
{
REQUIRE (not empty()); // trick to safely decrement by one
size_t penultimate = incWrap (after_, slotCnt()-1);
return iterator{IdxLink{this, penultimate}};
}
private: /* ====== storage management implementation ====== */
@ -243,24 +253,30 @@ namespace mem {
return after_ < start_;
} // note: both are equal only when empty
size_t
slotCnt() const
{
return extents_.size();
}
/** @return number of allocated slots actually used */
size_t
activeSlotCnt() const
{
REQUIRE (start_ < extents_.size());
REQUIRE (after_ <= extents_.size());
REQUIRE (start_ < slotCnt());
REQUIRE (after_ <= slotCnt());
return not isWrapped()? after_ - start_
: (after_ - 0)
+(extents_.size() - start_);
+(slotCnt() - start_);
}
size_t
freeSlotCnt() const
{ // always keep one in reserve...
REQUIRE (activeSlotCnt() < extents_.size());
REQUIRE (activeSlotCnt() < slotCnt());
return extents_.size() - activeSlotCnt();
return slotCnt() - activeSlotCnt();
}
bool
@ -273,19 +289,19 @@ namespace mem {
/** increment index, but wrap at array end.
* @remark using the array cyclically
*/
void
incWrap (size_t& idx, size_t inc =1)
size_t
incWrap (size_t idx, size_t inc =1)
{
idx = (idx+inc) % extents_.size();
return (idx+inc) % slotCnt();
}
bool
isValidPos (size_t idx) const
{
REQUIRE (idx < extents_.size());
REQUIRE (idx < slotCnt());
REQUIRE (activeSlotCnt() > 0);
return isWrapped()? (start_ <= idx and idx < extents_.size())
return isWrapped()? (start_ <= idx and idx < slotCnt())
or idx < after_
: (start_ <= idx and idx < after_);
}
@ -319,7 +335,7 @@ namespace mem {
size_t first() { return exFam_.start_; }
size_t last() { return exFam_.after_; }
size_t size() { return exFam_.extents_.size(); }
size_t size() { return exFam_.slotCnt(); }
size_t active() { return exFam_.activeSlotCnt(); }
};

View file

@ -127,7 +127,7 @@ namespace test {
extents.openNew(2); // allot two extents for active use
CHECK (it);
CHECK (0 == it.getIndex());
CHECK (isSameObject(*it, extents.first()));
CHECK (isSameObject(*it, *extents.begin()));
Extent& extent{*it};
CHECK (10 == extent.size());
@ -141,7 +141,7 @@ namespace test {
CHECK (1 == it.getIndex());
Extent& nextEx{*it};
CHECK (not isSameObject(extent, nextEx));
CHECK (isSameObject(nextEx, extents.last()));
CHECK (isSameObject(nextEx, *extents.last()));
nextEx[5] = extent[2] + 1;
CHECK (num == extent[2]);
CHECK (num+1 == nextEx[5]);

View file

@ -79815,8 +79815,8 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1689246500734" ID="ID_1611070672" MODIFIED="1689246508365" TEXT="Zugriff / Navigation">
<icon BUILTIN="flag-yellow"/>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1689246500734" ID="ID_1611070672" MODIFIED="1689265537668" TEXT="Zugriff / Navigation">
<icon BUILTIN="pencil"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1689246852684" ID="ID_1894820075" MODIFIED="1689246867447" TEXT="Problemlage">
<icon BUILTIN="info"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1689246509452" ID="ID_1697713434" MODIFIED="1689246780008">
@ -79837,7 +79837,7 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<icon BUILTIN="messagebox_warning"/>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1689247882172" ID="ID_1363520850" MODIFIED="1689247914843" TEXT="m&#xf6;gliche Ans&#xe4;tze">
<node COLOR="#435e98" CREATED="1689247882172" ID="ID_1363520850" MODIFIED="1689265724651" TEXT="m&#xf6;gliche Ans&#xe4;tze">
<icon BUILTIN="yes"/>
<node COLOR="#5b280f" CREATED="1689247931732" ID="ID_1905005266" MODIFIED="1689248098163" TEXT="einen Epochen-Iterator zum universellen Agens ausbauen">
<icon BUILTIN="button_cancel"/>
@ -79867,14 +79867,14 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</node>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1689248121694" ID="ID_1139194353" MODIFIED="1689248191851" TEXT="Zugriffs-Hierarchie: Allocator::iterator &#x29d0; EpochIter &#x29d0; Epoch&amp;">
<icon BUILTIN="forward"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1689248200483" ID="ID_1272289829" MODIFIED="1689248241810" TEXT="&#x27f9; Konsequenz: Extent&amp; nicht eigenst&#xe4;ndig verwenden"/>
<node COLOR="#435e98" CREATED="1689248200483" ID="ID_1272289829" MODIFIED="1689265563074" TEXT="&#x27f9; Konsequenz: Extent&amp; nicht eigenst&#xe4;ndig verwenden"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1689248250300" ID="ID_652125369" MODIFIED="1689248275834" TEXT="Epoch wird dann alle Abk&#xfc;rzungen und Hilfsfunktionen tragen">
<icon BUILTIN="idea"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1689248380839" ID="ID_215002237" MODIFIED="1689248398479" TEXT="BlockFlow stellt dann nur die grundlegenden Konverter bereit">
<icon BUILTIN="yes"/>
</node>
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1689248522225" ID="ID_1824695865" MODIFIED="1689248543003" TEXT="problematisches Layering der Iteratoren">
<node COLOR="#435e98" CREATED="1689248522225" ID="ID_1824695865" MODIFIED="1689265504640" TEXT="problematisches Layering der Iteratoren">
<icon BUILTIN="messagebox_warning"/>
<node CREATED="1689248585384" ID="ID_1368624985" MODIFIED="1689248594457" TEXT="IterStateWrapper hat eine private Core"/>
<node CREATED="1689248595565" ID="ID_1453930677" MODIFIED="1689248612015" TEXT="in IterExplorer g&#xe4;be es eine Variante mit Vererbung"/>
@ -79916,6 +79916,22 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</node>
</node>
</node>
<node COLOR="#338800" CREATED="1689265603545" ID="ID_791858650" MODIFIED="1689265662325" TEXT="BlockFlow stellt einen EpochIter bereit">
<icon BUILTIN="button_ok"/>
<node CREATED="1689265613521" ID="ID_1391121959" MODIFIED="1689265629139" TEXT="das ist noch einmal ein kompletter IterableDecorator"/>
<node CREATED="1689265630439" ID="ID_1230547158" MODIFIED="1689265658063" TEXT="aber setzt auf der &#xbb;state core&#xab; von ExtentFamily::iterator auf">
<icon BUILTIN="idea"/>
</node>
<node COLOR="#338800" CREATED="1689265676041" ID="ID_129160018" MODIFIED="1689265714844" TEXT="BlockFlow::AllocatorHandle wrappt nun diesen EpochIter">
<icon BUILTIN="button_ok"/>
</node>
<node COLOR="#338800" CREATED="1689265693878" ID="ID_391870028" MODIFIED="1689265713540" TEXT="alle weiteren Iterator-basierten Funktionen dereferenzieren damit auf Epoch&amp;">
<icon BUILTIN="button_ok"/>
</node>
<node COLOR="#338800" CREATED="1689265754039" ID="ID_199514628" MODIFIED="1689265769886" TEXT="Epoch::setup (rawStorageIter) extrahiert">
<icon BUILTIN="button_ok"/>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1688516187452" ID="ID_843039397" MODIFIED="1688516195292" TEXT="Zusatz-Infos zu verwalten">
<icon BUILTIN="flag-yellow"/>