BufferProvider: devise a heuristic for buffer selection
- take into account how much memory will be wasted when using some allocation to satisfy a (smaller) allocation request - furthermore prefer allocations used frequently in the past - when an allocation is used, tag it with a score that also reflects this degree of waste - select buffers with low score first for clean-up
This commit is contained in:
parent
dd37600c4d
commit
b59cb12974
3 changed files with 202 additions and 19 deletions
|
|
@ -13,6 +13,31 @@
|
|||
|
||||
/** @file local-mem-pool.hpp
|
||||
** A local cache of buffer allocations to facilitate reuse of memory in a worker.
|
||||
** Since the Lumiera Render Engine relies on scheduled jobs running concurrently,
|
||||
** it is essential to avoid any kind of locking that might prevent the calculations
|
||||
** from moving past each other. One crucial functionality that might lead to such
|
||||
** a subtle cross-wise obstruction is the handling of buffer memory allocations.
|
||||
** Each worker thread in the engine thus needs a (small) thread-local pool of
|
||||
** buffer allocations to handle all foreseeable allocation requests. Whenever
|
||||
** a render job starts, the information regarding a _maximal allocation footprint_
|
||||
** can be retrieved from the ProcNode, since this information is determined solely
|
||||
** by the expected call topology. Based on this information, additional memory
|
||||
** can be requested from a global buffer memory allocator; and while any new
|
||||
** allocation will take some time, the alloted buffer entries can be sent down
|
||||
** to the worker's local allocation pool through a lock-free queue, so that —
|
||||
** hopefully — these entries have already arrived at the point when the memory
|
||||
** is actually required from within the recursive Render Node `pull()` call.
|
||||
**
|
||||
** LocalMemPool is responsible for receiving those memory provisions and for
|
||||
** managing and assigning available buffer blocks, drawing from the pool of
|
||||
** locally known allocations. For every new allocation request, an attempt
|
||||
** is made to find a best match within the available allotments of memory.
|
||||
** In the exceptional case however when no suitable prepared allocation
|
||||
** can be found locally, new memory must be acquired right away, since
|
||||
** render jobs are assumed to run through without blocking waits.
|
||||
**
|
||||
** To avoid the hazard of the pool accumulating an increasing number of surplus
|
||||
** allocations over time, less frequently used blocks are culled using a heuristic.
|
||||
**
|
||||
** @see local-mem-pool-test.cpp
|
||||
** @see local-buffer-store.hpp
|
||||
|
|
@ -49,7 +74,11 @@ namespace mem {
|
|||
|
||||
namespace { // Configuration tuning parameters
|
||||
|
||||
const size_t INQUEUE_SIZ = 30; ///< initial size of the lock-free provision queue
|
||||
const size_t INQUEUE_SIZ = 30; ///< initial size of the lock-free provision queue
|
||||
|
||||
const uint MATCH_SCORE = 10; ///< score to add when a buffer can be used to satisfy a request
|
||||
const uint UNUSABLE_MALUS = 2; ///< reduce score whenever a buffer is too small to be useful
|
||||
const double USAGE_WEIGHT = 0.9; ///< degree to which very frequent usage counteracts waste of memory
|
||||
|
||||
}//(End) config parameters and internals
|
||||
|
||||
|
|
@ -92,6 +121,8 @@ namespace mem {
|
|||
InQueue inQueue_;
|
||||
BlockList blocks_;
|
||||
|
||||
int32_t maxScore_{1};
|
||||
|
||||
public:
|
||||
LocalMemPool()
|
||||
: inQueue_{INQUEUE_SIZ}
|
||||
|
|
@ -138,11 +169,26 @@ namespace mem {
|
|||
* \ref retrieve() an allocation will use available memory.
|
||||
* There is no _handle_ to refer to some specific reservation.
|
||||
*/
|
||||
size_t
|
||||
uint
|
||||
reserve (uint cnt, size_t sizRequest)
|
||||
{
|
||||
ingest();
|
||||
return 0; ///////////////////////////////////////OOO
|
||||
uint reserved{0};
|
||||
for (Block& block : blocks_)
|
||||
if (reserved >= cnt)
|
||||
break;
|
||||
else
|
||||
if (not (block.used or block.resd)
|
||||
and block.alloc.siz == sizRequest)
|
||||
{ // perfect match, reserve this allocation
|
||||
block.resd = true;
|
||||
++reserved;
|
||||
}
|
||||
if (reserved < cnt)
|
||||
{ // now look for imperfect matches
|
||||
TODO ("how to select best matches?");
|
||||
}
|
||||
return reserved;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -198,7 +244,6 @@ namespace mem {
|
|||
else
|
||||
{
|
||||
ENSURE (not found->used);
|
||||
found->score += 1; ///////////////////TODO
|
||||
found->resd = false;
|
||||
found->used = true;
|
||||
return found->alloc;
|
||||
|
|
@ -212,11 +257,26 @@ namespace mem {
|
|||
* @return actual number of allocations removed from the pool.
|
||||
*/
|
||||
template<class FUN> ////////TODO requires std::invocable<FUN,Buff*,size_t>
|
||||
size_t
|
||||
uint
|
||||
yield (uint cnt, size_t siz, FUN&& consumer)
|
||||
{
|
||||
ingest();
|
||||
return 0;///////////////////////OOO
|
||||
uint removed{0};
|
||||
for (auto pos = blocks_.begin()
|
||||
; pos != blocks_.end()
|
||||
; ++pos)
|
||||
if (cnt == 0)
|
||||
break;
|
||||
else
|
||||
if (not pos->used
|
||||
and siz == pos->alloc.siz)
|
||||
{
|
||||
consumer (pos->alloc.mem, pos->alloc.siz);
|
||||
blocks_.erase (pos);
|
||||
++removed;
|
||||
--cnt;
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -228,13 +288,30 @@ namespace mem {
|
|||
* @return actual number of allocations removed from the pool.
|
||||
*/
|
||||
template<class FUN> ////////TODO requires std::invocable<FUN,Buff*,size_t>
|
||||
size_t
|
||||
uint
|
||||
cleanup (double degree, FUN&& consumer)
|
||||
{
|
||||
ingest();
|
||||
return 0;///////////////////////OOO
|
||||
uint removed{0};
|
||||
int32_t killLevel = degree * maxScore_;
|
||||
for (auto pos = blocks_.begin()
|
||||
; pos != blocks_.end()
|
||||
; ++pos)
|
||||
if (not (pos->used or pos->resd)
|
||||
and pos->score <= killLevel)
|
||||
{ // send this allocation away and remove it from the pool
|
||||
consumer (pos->alloc.mem, pos->alloc.siz);
|
||||
blocks_.erase (pos);
|
||||
++removed;
|
||||
}
|
||||
else
|
||||
pos->score -= killLevel;
|
||||
// recalibrate all score levels
|
||||
maxScore_ -= killLevel;
|
||||
return removed;
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
void
|
||||
ingest()
|
||||
|
|
@ -274,18 +351,32 @@ namespace mem {
|
|||
selectBestMatch (size_t sizRequest)
|
||||
{
|
||||
Block* match{nullptr};
|
||||
size_t waste(-1);
|
||||
double bestSelector{0.0};
|
||||
for (Block& block : blocks_)
|
||||
if (not block.used
|
||||
and sizRequest <= block.alloc.siz)
|
||||
if (not block.used)
|
||||
{
|
||||
size_t wasted = block.alloc.siz - sizRequest;
|
||||
if (wasted < waste)
|
||||
if (sizRequest > block.alloc.siz)
|
||||
block.score -= UNUSABLE_MALUS;
|
||||
else
|
||||
{
|
||||
waste = wasted;
|
||||
match = █
|
||||
double wasted = block.alloc.siz - sizRequest;
|
||||
double wasteScore = wasted / block.alloc.siz;
|
||||
double usageFact = block.score / maxScore_;
|
||||
double selector = 1.0 - wasteScore * (1.0 - usageFact * USAGE_WEIGHT);
|
||||
if (selector > bestSelector)
|
||||
{
|
||||
bestSelector = selector;
|
||||
match = █
|
||||
}
|
||||
}
|
||||
}
|
||||
if (match)
|
||||
{
|
||||
ENSURE (match->alloc.siz >= sizRequest);
|
||||
match->score += int(MATCH_SCORE * (1 - (1 - double(sizRequest) / match->alloc.siz)));
|
||||
if (match->score > maxScore_) // reduce score by waste factor as malus
|
||||
maxScore_ = match->score;
|
||||
}
|
||||
return match;
|
||||
}
|
||||
|
||||
|
|
@ -293,6 +384,8 @@ namespace mem {
|
|||
};
|
||||
|
||||
|
||||
|
||||
|
||||
class PoolDiagnostic
|
||||
: util::MoveOnly
|
||||
{
|
||||
|
|
@ -316,6 +409,13 @@ namespace mem {
|
|||
return std::ranges::count_if (memPool_.blocks_
|
||||
,[siz](Block const& b){ return siz == b.alloc.siz; });
|
||||
}
|
||||
|
||||
size_t
|
||||
cntFree()
|
||||
{
|
||||
return std::ranges::count_if (memPool_.blocks_
|
||||
,[](Block const& b){ return not b.used; });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -112,6 +112,28 @@ namespace test {
|
|||
CHECK (pool.canServe(SIZ10)); // query causes a scan and retrieval from the in-queu
|
||||
CHECK (watch(pool).cnt(SIZ10) == 1);
|
||||
CHECK (not watch(pool).isEmpty());
|
||||
|
||||
pool.add (MEM2, SIZ20);
|
||||
pool.add (MEM3, SIZ30);
|
||||
CHECK (pool.canServe(SIZ20));
|
||||
CHECK (pool.canServe(SIZ10));
|
||||
CHECK (pool.size() == 3);
|
||||
CHECK (watch(pool).cnt(SIZ10) == 1);
|
||||
CHECK (watch(pool).cnt(SIZ20) == 1);
|
||||
CHECK (watch(pool).cnt(SIZ30) == 1);
|
||||
CHECK (watch(pool).cntFree() == 3);
|
||||
|
||||
auto [m1,s1] = pool.retrieve (SIZ20 -2);
|
||||
CHECK (SIZ20 == s1);
|
||||
CHECK (MEM2 == m1);
|
||||
CHECK (watch(pool).cntFree() == 2);
|
||||
auto [m2,s2] = pool.retrieve (SIZ10);
|
||||
CHECK (SIZ10 == s2);
|
||||
CHECK (MEM1 == m2);
|
||||
CHECK (watch(pool).cntFree() == 1);
|
||||
CHECK (pool.canServe(SIZ10));
|
||||
CHECK (pool.canServe(SIZ20));
|
||||
CHECK (pool.canServe(SIZ30));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -145876,9 +145876,10 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
</node>
|
||||
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1777050453251" ID="ID_1529547535" MODIFIED="1777066700207" TEXT="Implementieren">
|
||||
<icon BUILTIN="pencil"/>
|
||||
<node CREATED="1777052048904" ID="ID_1403107789" MODIFIED="1777052073352" TEXT="die Eingangs-Queue wird komplett (transparent) integriert">
|
||||
<node COLOR="#338800" CREATED="1777052048904" ID="ID_1403107789" MODIFIED="1777084853433" TEXT="die Eingangs-Queue wird komplett (transparent) integriert">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node BACKGROUND_COLOR="#c8c0b6" COLOR="#435e98" CREATED="1777078419796" ID="ID_1966959598" MODIFIED="1777084265167" TEXT="verwende boost::lockfree::queue"/>
|
||||
<node BACKGROUND_COLOR="#f0d5c5" COLOR="#990033" CREATED="1777078428098" ID="ID_1939142827" MODIFIED="1777078435459" TEXT="ist das die richtige Wahl?">
|
||||
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#470f69" CREATED="1777078428098" ID="ID_1939142827" MODIFIED="1777084838373" TEXT="ist das die richtige Wahl?">
|
||||
<icon BUILTIN="help"/>
|
||||
<node CREATED="1777078440070" ID="ID_885005752" MODIFIED="1777078470502" TEXT="verlangt »trivial assignment« und Destructor"/>
|
||||
<node CREATED="1777078471365" ID="ID_769328039" MODIFIED="1777078487222" TEXT="heißt im Klartext: darf keine default-Initialiser haben">
|
||||
|
|
@ -145887,7 +145888,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
</node>
|
||||
<node COLOR="#435e98" CREATED="1777078635327" ID="ID_238173965" MODIFIED="1777084271658" TEXT="muß mir selber eine passende Struct bauen"/>
|
||||
</node>
|
||||
<node CREATED="1777078489787" ID="ID_1970735736" MODIFIED="1777084225822" TEXT="Vorteile">
|
||||
<node BACKGROUND_COLOR="#c8c0b6" COLOR="#435e98" CREATED="1777078489787" ID="ID_1970735736" MODIFIED="1777084845753" TEXT="Vorteile">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head/>
|
||||
<body>
|
||||
|
|
@ -145909,7 +145910,9 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
</node>
|
||||
</node>
|
||||
<node CREATED="1777052358645" ID="ID_1367907395" MODIFIED="1777052396869" TEXT="zum Aufräumen wird ein Empfänger-λ übergeben"/>
|
||||
<node CREATED="1777052497154" ID="ID_1906132363" MODIFIED="1777052517239" TEXT="vorerst den default(-Heap)-Allokator für alles verwenden"/>
|
||||
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#620dbd" CREATED="1777052497154" ID="ID_1906132363" MODIFIED="1777084874533" TEXT="vorerst den default(-Heap)-Allokator für alles verwenden">
|
||||
<icon BUILTIN="yes"/>
|
||||
</node>
|
||||
<node CREATED="1777073876105" ID="ID_1153882258" MODIFIED="1777073995987" TEXT="stelle fest: LinkedElements sind nicht gut geeignet">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head/>
|
||||
|
|
@ -145924,6 +145927,64 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
</node>
|
||||
<node CREATED="1777074521660" ID="ID_1763982951" MODIFIED="1777074532534" TEXT="grundsätzlich: gehe von einer kleinen Anzahl an Einträgen aus"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1777159795008" ID="ID_851031995" MODIFIED="1777168836233" TEXT="Heuristik zum Aufräumen schaffen">
|
||||
<icon BUILTIN="pencil"/>
|
||||
<node CREATED="1777160636295" ID="ID_1174478817" MODIFIED="1777160642510" TEXT="gewünschter Effekt">
|
||||
<node CREATED="1777160710978" ID="ID_393248452" MODIFIED="1777160729106" TEXT="möglichst wenige und möglichst gut passende Buffer lokal halten"/>
|
||||
<node CREATED="1777162022979" ID="ID_763110420" MODIFIED="1777162035764" TEXT="Prio-1 ist stets den am besten passenden Buffer zu verwenden"/>
|
||||
<node CREATED="1777162072416" ID="ID_133387683" MODIFIED="1777162520272" TEXT="allerdings soll unter einem gewissen »misfit«-Level dann MRU wichtiger werden">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
denn sonst hält nach einiger Zeit jeder Pool eine Menge selten genutzter Buffer, obwohl man mit einem Bruchteil davon auch über die Runden kommen könnte
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1777162521383" ID="ID_215039061" MODIFIED="1777162527306" TEXT="Ansatz">
|
||||
<node CREATED="1777162528726" ID="ID_875736163" MODIFIED="1777162538808" TEXT="vergebe einen Score für erfolgreichen Match"/>
|
||||
<node CREATED="1777162540196" ID="ID_1866543398" MODIFIED="1777162577399" TEXT="reduziere den Score für zu großen Buffer"/>
|
||||
<node CREATED="1777162589638" ID="ID_175029995" MODIFIED="1777162673190" TEXT="ziehe Punkte ab für zu kleinen (unbauchbaren) Buffer"/>
|
||||
<node CREATED="1777162681518" ID="ID_1045190433" MODIFIED="1777162711729" TEXT="clean-up verwirft Buffer,,,">
|
||||
<node CREATED="1777162712892" ID="ID_1388888751" MODIFIED="1777162719520" TEXT="die nicht reserviert oder belegt sind"/>
|
||||
<node CREATED="1777162720405" ID="ID_718342875" MODIFIED="1777162812203" TEXT="deren Score prozentual unterhalb dem Max-Score liegen">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
wobei dieser Prozentsatz aus dem <font face="Monospaced" color="#683d3d">degree</font> - Parameter abgeleittet wird (mit umgekehrter Orientierung)
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1777162841060" ID="ID_1652390427" MODIFIED="1777162844852" TEXT="Punktsätze">
|
||||
<node CREATED="1777162851571" ID="ID_1498523122" MODIFIED="1777162858662" TEXT="+10 Punkte für erfolgreichen Gebrauch"/>
|
||||
<node CREATED="1777162861786" ID="ID_990114851" MODIFIED="1777162887674" TEXT="bis zu 9 Punkte Abzug davon für zu großen Buffer"/>
|
||||
<node CREATED="1777162888442" ID="ID_416693693" MODIFIED="1777162913895" TEXT="jeder unbrauchbare Buffer bekommt einen Malus von -2"/>
|
||||
<node CREATED="1777162914715" ID="ID_577486589" MODIFIED="1777162923685" TEXT="Score kann nicht negativ werden"/>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1777162988481" ID="ID_1170538606" MODIFIED="1777162995671" TEXT="Implementierung">
|
||||
<node CREATED="1777162997152" ID="ID_833672862" MODIFIED="1777163012266" TEXT="berechne für jeden Buffer einen waste-Score (0..9)"/>
|
||||
<node CREATED="1777163103258" ID="ID_1723954782" MODIFIED="1777163118028" TEXT="oder besser: eine matchQuality 1..10"/>
|
||||
<node CREATED="1777164416380" ID="ID_740262476" MODIFIED="1777164435189" TEXT="auf diese wende ich den allgemeinen Score graduell an">
|
||||
<node CREATED="1777164437720" ID="ID_712610709" MODIFIED="1777164452490" TEXT="und zwar mit steigendem Gewicht für schlechteren Match"/>
|
||||
<node CREATED="1777164453966" ID="ID_1158139935" MODIFIED="1777164476724" TEXT="und als Malus für seltener verwendete Buffer"/>
|
||||
</node>
|
||||
<node CREATED="1777164501370" ID="ID_1271851142" MODIFIED="1777164513738" TEXT="daraus ergibt sich ein transienter selectionScore"/>
|
||||
<node CREATED="1777164514849" ID="ID_489761092" MODIFIED="1777164525297" TEXT="und gewählt wird nach maximalem selectionScore"/>
|
||||
<node CREATED="1777164528024" ID="ID_1047876896" MODIFIED="1777164541639" TEXT="dieser gewählte Buffer bekommt dann einen Score-Update"/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1777050457640" ID="ID_55967690" MODIFIED="1777084249904" TEXT="Dokumentieren per Test">
|
||||
<icon BUILTIN="pencil"/>
|
||||
|
|
|
|||
Loading…
Reference in a new issue