Block-Flow: introduce config through a policy mix-in

...measured running time reproduced unaltered for -O3
This commit is contained in:
Fischlurch 2023-07-20 19:28:20 +02:00
parent 5803fed544
commit ca502aa826
4 changed files with 242 additions and 150 deletions

View file

@ -54,6 +54,7 @@ namespace gear {
// using util::isnil;
// using std::string;
using BlockFlowAlloc = BlockFlow<blockFlow::RenderConfig>;
/**
@ -64,11 +65,11 @@ namespace gear {
*/
class ActivityLang
{
BlockFlow& mem_;
BlockFlowAlloc& mem_;
public:
// explicit
ActivityLang (BlockFlow& memManager)
ActivityLang (BlockFlowAlloc& memManager)
: mem_{memManager}
{ }

View file

@ -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<class CONF>
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 ALO>
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<EpochGate&> ((*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<Epoch&> (*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 CONF>
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<EpochGate&> ((*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<Epoch&> (*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 CONF = blockFlow::DefaultConfig>
class BlockFlow
: util::NonCopyable
: public blockFlow::Strategy<CONF>
, util::NonCopyable
{
constexpr static size_t EPOCH_SIZ = CONF::EPOCH_SIZ;
public:
using Allocator = mem::ExtentFamily<Activity, EPOCH_SIZ>;
using RawIter = typename Allocator::iterator;
using Extent = typename Allocator::Extent;
using Epoch = blockFlow::Epoch<Allocator>;
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<Epoch&> (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<CONF>;
};
@ -582,12 +652,15 @@ namespace gear {
/* ===== Test / Diagnostic ===== */
template<class CONF>
class FlowDiagnostic
{
BlockFlow& flow_;
using Epoch = typename BlockFlow<CONF>::Epoch;
BlockFlow<CONF>& flow_;
public:
FlowDiagnostic(BlockFlow& theFlow)
FlowDiagnostic(BlockFlow<CONF>& theFlow)
: flow_{theFlow}
{ }
@ -618,8 +691,9 @@ namespace gear {
}
};
inline FlowDiagnostic
watch (BlockFlow& theFlow)
template<class CONF>
inline FlowDiagnostic<CONF>
watch (BlockFlow<CONF>& theFlow)
{
return FlowDiagnostic{theFlow};
}

View file

@ -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<Epoch::SIZ(); ++i)
for (uint i=1; i<EXTENT_SIZ; ++i)
allocHandle.create();
CHECK (allocHandle.currDeadline() == Time(400,10));
@ -258,7 +264,7 @@ namespace test {
CHECK (watch(bFlow).find(a4) == "10s600ms"_expect);
// fill up and exhaust this Epoch too....
for (uint i=1; i<Epoch::SIZ(); ++i)
for (uint i=1; i<EXTENT_SIZ; ++i)
allocHandle.create();
// so the handle has moved to the after next Epoch
@ -271,7 +277,7 @@ namespace test {
// now repeat the same pattern, but now towards uncharted Epochs
allocHandle = bFlow.until(Time{900,10});
for (uint i=2; i<Epoch::SIZ(); ++i)
for (uint i=2; i<EXTENT_SIZ; ++i)
allocHandle.create();
CHECK (allocHandle.currDeadline() == Time(0,11));

View file

@ -80234,9 +80234,10 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<node CREATED="1689809584696" ID="ID_1801960603" MODIFIED="1689809593344" TEXT="bisher war alles einfach in einem anonymen Namespace"/>
<node CREATED="1689809593860" ID="ID_516321085" MODIFIED="1689809604951" TEXT="und dort war auch ein Typ &quot;Allocator&quot; definiert"/>
</node>
<node CREATED="1689809607614" ID="ID_236289241" MODIFIED="1689809624139" TEXT="Ansatz: policy-Mix-In">
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1689809625112" ID="ID_1243565823" MODIFIED="1689809788462" TEXT="wird in einem sub-Namespace blockFlow bereitgestellt">
<icon BUILTIN="flag-yellow"/>
<node COLOR="#435e98" CREATED="1689809607614" ID="ID_236289241" MODIFIED="1689872669620" TEXT="Ansatz: policy-Mix-In">
<icon BUILTIN="idea"/>
<node COLOR="#435e98" CREATED="1689809625112" ID="ID_1243565823" MODIFIED="1689872637282" TEXT="wird in einem sub-Namespace blockFlow bereitgestellt">
<icon BUILTIN="yes"/>
<node CREATED="1689809792157" ID="ID_1564252749" MODIFIED="1689809807279" TEXT="DefaultConfig">
<node CREATED="1689809818314" ID="ID_1792198273" MODIFIED="1689809823013" TEXT="als default-Argument"/>
<node CREATED="1689809827345" ID="ID_81051745" MODIFIED="1689809834680" TEXT="wird f&#xfc;r die Tests verwendet">
@ -80245,10 +80246,10 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</node>
<node CREATED="1689809808051" ID="ID_649095088" MODIFIED="1689809811895" TEXT="RenderConfig"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1689809665379" ID="ID_1833888873" MODIFIED="1689809754826" TEXT="per Template-Parameter eingemischt">
<icon BUILTIN="flag-yellow"/>
<node COLOR="#338800" CREATED="1689809665379" ID="ID_1833888873" MODIFIED="1689872648583" TEXT="per Template-Parameter eingemischt">
<icon BUILTIN="button_ok"/>
<node CREATED="1689810756996" ID="ID_97852927" MODIFIED="1689810767657" TEXT="soll Konfigurierbarkeit schaffen"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1689810768845" ID="ID_1900574250" MODIFIED="1689860411216" TEXT="Konsequenz &#x27f9; mu&#xdf; eine Strategy abspalten">
<node COLOR="#435e98" CREATED="1689810768845" ID="ID_1900574250" MODIFIED="1689872627938" TEXT="Konsequenz &#x27f9; mu&#xdf; eine Strategy abspalten">
<richcontent TYPE="NOTE"><html>
<head/>
<body>
@ -80258,8 +80259,8 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</body>
</html></richcontent>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1689860417547" ID="ID_1858836680" MODIFIED="1689860435193" TEXT="Zugriff von dort auf die Config als Meyers Singleton">
<icon BUILTIN="flag-yellow"/>
<node COLOR="#338800" CREATED="1689860417547" ID="ID_1858836680" MODIFIED="1689872568369" TEXT="Zugriff von dort auf die Config als Meyers Singleton">
<icon BUILTIN="button_ok"/>
<node CREATED="1689860436808" ID="ID_666663778" MODIFIED="1689863231116" TEXT="Diskussion...">
<icon BUILTIN="info"/>
<node CREATED="1689860443279" ID="ID_953236469" MODIFIED="1689861105486" TEXT="man k&#xf6;nnte auch direkt die Felder non-static einmischen">
@ -80303,29 +80304,28 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</html></richcontent>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1689863244607" ID="ID_824360010" MODIFIED="1689863256431" TEXT="als Getter direkt in die Config-Klasse eingebaut">
<icon BUILTIN="flag-yellow"/>
<node COLOR="#338800" CREATED="1689863244607" ID="ID_824360010" MODIFIED="1689872622784" TEXT="als Getter in die Strategy-Basisklasse eingebaut">
<icon BUILTIN="button_ok"/>
</node>
<node CREATED="1689863261456" ID="ID_824983335" MODIFIED="1689863270316" TEXT="damit per Typ aufl&#xf6;sbar">
<icon BUILTIN="idea"/>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1689809675666" ID="ID_899609884" MODIFIED="1689809753618" TEXT="&#x27f9; damit wird auch Epoch parametrisiert">
<icon BUILTIN="flag-yellow"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1689809757289" ID="ID_1758760519" MODIFIED="1689809785454" TEXT="und zwar auf den Allocator">
<icon BUILTIN="flag-yellow"/>
<node COLOR="#338800" CREATED="1689809675666" ID="ID_899609884" MODIFIED="1689872656941" TEXT="&#x27f9; damit wird auch Epoch parametrisiert">
<node COLOR="#338800" CREATED="1689809757289" ID="ID_1758760519" MODIFIED="1689872663074" TEXT="und zwar auf den Allocator">
<icon BUILTIN="button_ok"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1689809767024" ID="ID_263502473" MODIFIED="1689809784790" TEXT="der einfache Name Epoch wandert nun in BlockFlow">
<icon BUILTIN="flag-yellow"/>
<node COLOR="#338800" CREATED="1689809767024" ID="ID_263502473" MODIFIED="1689872663073" TEXT="der einfache Name Epoch wandert nun in BlockFlow">
<icon BUILTIN="button_ok"/>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1689818224323" ID="ID_1914141471" MODIFIED="1689818233142" TEXT="Strategy baut darauf auf">
<icon BUILTIN="flag-yellow"/>
<node COLOR="#338800" CREATED="1689818224323" ID="ID_1914141471" MODIFIED="1689872666750" TEXT="Strategy baut darauf auf">
<icon BUILTIN="button_ok"/>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1689818168020" ID="ID_745780964" MODIFIED="1689818211837" TEXT="Parameter reorganisieren">
<icon BUILTIN="flag-yellow"/>
<node COLOR="#338800" CREATED="1689818168020" ID="ID_745780964" MODIFIED="1689872676809" TEXT="Parameter reorganisieren">
<icon BUILTIN="button_ok"/>
<node CREATED="1689818175388" ID="ID_1172559425" MODIFIED="1689818181757" TEXT="charakteristische Parameter"/>
<node CREATED="1689818182227" ID="ID_385651938" MODIFIED="1689818185945" TEXT="Tuning-Parameter"/>
<node CREATED="1689818186633" ID="ID_76340851" MODIFIED="1689818191732" TEXT="Kontext-Annahmen"/>
@ -80867,6 +80867,17 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1689809417524" ID="ID_1054295274" MODIFIED="1689812997432" TEXT="setzt Umbau der Parametrisierung voraus">
<arrowlink COLOR="#7a2dcb" DESTINATION="ID_469668336" ENDARROW="Default" ENDINCLINATION="-742;705;" ID="Arrow_ID_1498620407" STARTARROW="None" STARTINCLINATION="435;-802;"/>
<icon BUILTIN="flag-yellow"/>
<node COLOR="#338800" CREATED="1689873255900" ID="ID_49309408" MODIFIED="1689873446639" TEXT="erst mal: Zugriff via Meyer&apos;s Singleton einf&#xfc;hren">
<icon BUILTIN="button_ok"/>
<node COLOR="#435e98" CREATED="1689873426661" ID="ID_476877906" MODIFIED="1689873442030" TEXT="Parameter sollen sich im Default-Fall identisch ergeben">
<icon BUILTIN="yes"/>
</node>
<node COLOR="#338800" CREATED="1689873270874" ID="ID_66965861" MODIFIED="1689873410951" TEXT="Laufzeiten">
<icon BUILTIN="button_ok"/>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1689873274593" ID="ID_116423794" MODIFIED="1689873404864" TEXT="-O0 : 630ns"/>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1689873396056" ID="ID_1088441301" MODIFIED="1689873404865" TEXT="-O3 : 46ns"/>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1689813003462" ID="ID_1047452990" MODIFIED="1689813037510" TEXT="vermutlich gibt es aber eine nat&#xfc;rliche Obergrenze">
<icon BUILTIN="messagebox_warning"/>