BufferProvider: decide to use only a simple heuristics for reservation
At the begin of each Render Node `pull()`, the render job is expected to ''announce'' a maximum expected allocation requirement to the `BufferProvider`, which information is passed-on to the `LocalMemPool` to determine to which degree it can be satisfied with allocations from that local pool. Without actual empiric observation, it is impossible to determine the relevance of this step, how it should be resolved heuristically against the known contents of the pool, and how much effort to spend on that step. Thus I'll settle for the ''simplest thing that can possibly work'' — which is to check only for close matches and rather rely on additional allocations, forwarded asynchronously, assuming that the clean-up heuristics will deal with excess allocations quickly.
This commit is contained in:
parent
b59cb12974
commit
5fafb16807
3 changed files with 138 additions and 17 deletions
|
|
@ -79,12 +79,14 @@ namespace mem {
|
||||||
const uint MATCH_SCORE = 10; ///< score to add when a buffer can be used to satisfy a request
|
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 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
|
const double USAGE_WEIGHT = 0.9; ///< degree to which very frequent usage counteracts waste of memory
|
||||||
|
const double CLOSE_MATCH = 0.2; ///< fraction of wasted memory that still counts as /good match/
|
||||||
|
|
||||||
}//(End) config parameters and internals
|
}//(End) config parameters and internals
|
||||||
|
|
||||||
using std::move;
|
using std::move;
|
||||||
using std::forward;
|
using std::forward;
|
||||||
using util::unConst;
|
using util::unConst;
|
||||||
|
using util::isLimited;
|
||||||
|
|
||||||
class PoolDiagnostic;
|
class PoolDiagnostic;
|
||||||
|
|
||||||
|
|
@ -164,10 +166,14 @@ namespace mem {
|
||||||
* An allocation promised hereby will be marked as reserved internally,
|
* An allocation promised hereby will be marked as reserved internally,
|
||||||
* and will thus not be considered for further `reserve()` requests.
|
* and will thus not be considered for further `reserve()` requests.
|
||||||
* @return number of allocations of the indicated size,
|
* @return number of allocations of the indicated size,
|
||||||
* that are available immediately and have been reserved now.
|
* that are available immediately and have been reserved now.
|
||||||
* @note irrespective of any reservation, an immediate request to
|
* @note only allocations larger yet comparatively close to the desired
|
||||||
* \ref retrieve() an allocation will use available memory.
|
* size are considered; instead of reserving an over-allocation, it seems
|
||||||
* There is no _handle_ to refer to some specific reservation.
|
* preferable to request a fitting new one asynchronously from the global
|
||||||
|
* memory pool, given that there is still some time left until it's actually
|
||||||
|
* needed. However, irrespective of any reservation, an immediate request to
|
||||||
|
* \ref retrieve() an allocation will use any locally available memory.
|
||||||
|
* There is no _handle_ to refer to some specific reservation.
|
||||||
*/
|
*/
|
||||||
uint
|
uint
|
||||||
reserve (uint cnt, size_t sizRequest)
|
reserve (uint cnt, size_t sizRequest)
|
||||||
|
|
@ -179,15 +185,17 @@ namespace mem {
|
||||||
break;
|
break;
|
||||||
else
|
else
|
||||||
if (not (block.used or block.resd)
|
if (not (block.used or block.resd)
|
||||||
and block.alloc.siz == sizRequest)
|
and isLimited(0.0, 1 - double(sizRequest)/block.alloc.siz, CLOSE_MATCH))
|
||||||
{ // perfect match, reserve this allocation
|
{ // good match, reserve this allocation
|
||||||
block.resd = true;
|
block.resd = true;
|
||||||
++reserved;
|
++reserved;
|
||||||
}
|
}
|
||||||
if (reserved < cnt)
|
/* Note we did not consider to use much larger blocks
|
||||||
{ // now look for imperfect matches
|
* even while they might be used to satisfy the request.
|
||||||
TODO ("how to select best matches?");
|
* In theory, we could even sort matches by amount of waste,
|
||||||
}
|
* but it's doubtful this would be better than requesting
|
||||||
|
* a really fitting new allocation from the global manager.
|
||||||
|
*/
|
||||||
return reserved;
|
return reserved;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -264,7 +272,7 @@ namespace mem {
|
||||||
uint removed{0};
|
uint removed{0};
|
||||||
for (auto pos = blocks_.begin()
|
for (auto pos = blocks_.begin()
|
||||||
; pos != blocks_.end()
|
; pos != blocks_.end()
|
||||||
; ++pos)
|
; )
|
||||||
if (cnt == 0)
|
if (cnt == 0)
|
||||||
break;
|
break;
|
||||||
else
|
else
|
||||||
|
|
@ -272,10 +280,13 @@ namespace mem {
|
||||||
and siz == pos->alloc.siz)
|
and siz == pos->alloc.siz)
|
||||||
{
|
{
|
||||||
consumer (pos->alloc.mem, pos->alloc.siz);
|
consumer (pos->alloc.mem, pos->alloc.siz);
|
||||||
blocks_.erase (pos);
|
pos = blocks_.erase (pos);
|
||||||
++removed;
|
++removed;
|
||||||
--cnt;
|
--cnt;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
++pos;
|
||||||
|
|
||||||
return removed;
|
return removed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -296,16 +307,19 @@ namespace mem {
|
||||||
int32_t killLevel = degree * maxScore_;
|
int32_t killLevel = degree * maxScore_;
|
||||||
for (auto pos = blocks_.begin()
|
for (auto pos = blocks_.begin()
|
||||||
; pos != blocks_.end()
|
; pos != blocks_.end()
|
||||||
; ++pos)
|
; )
|
||||||
if (not (pos->used or pos->resd)
|
if (not (pos->used or pos->resd)
|
||||||
and pos->score <= killLevel)
|
and pos->score <= killLevel)
|
||||||
{ // send this allocation away and remove it from the pool
|
{ // send this allocation away and remove it from the pool
|
||||||
consumer (pos->alloc.mem, pos->alloc.siz);
|
consumer (pos->alloc.mem, pos->alloc.siz);
|
||||||
blocks_.erase (pos);
|
pos = blocks_.erase (pos);
|
||||||
++removed;
|
++removed;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
pos->score -= killLevel;
|
{
|
||||||
|
pos->score -= killLevel;
|
||||||
|
++pos;
|
||||||
|
}
|
||||||
// recalibrate all score levels
|
// recalibrate all score levels
|
||||||
maxScore_ -= killLevel;
|
maxScore_ -= killLevel;
|
||||||
return removed;
|
return removed;
|
||||||
|
|
@ -403,6 +417,12 @@ namespace mem {
|
||||||
return memPool_.empty();
|
return memPool_.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t
|
||||||
|
size()
|
||||||
|
{
|
||||||
|
return memPool_.size();
|
||||||
|
}
|
||||||
|
|
||||||
size_t
|
size_t
|
||||||
cnt (size_t siz)
|
cnt (size_t siz)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -20,13 +20,17 @@
|
||||||
#include "test/test-helper.hpp"
|
#include "test/test-helper.hpp"
|
||||||
//#include "vault/mem/engine-buffer-metadata.hpp"
|
//#include "vault/mem/engine-buffer-metadata.hpp"
|
||||||
#include "vault/mem/local-mem-pool.hpp"
|
#include "vault/mem/local-mem-pool.hpp"
|
||||||
|
#include "lib/iter-stack.hpp"
|
||||||
//#include "lib/depend-inject.hpp"
|
//#include "lib/depend-inject.hpp"
|
||||||
//#include "lib/thread.hpp"
|
//#include "lib/thread.hpp"
|
||||||
//#include "lib/error.hpp"
|
//#include "lib/error.hpp"
|
||||||
|
#include "lib/format-util.hpp"
|
||||||
|
#include "test/diagnostic-output.hpp"
|
||||||
|
|
||||||
|
|
||||||
//using std::this_thread::yield;
|
//using std::this_thread::yield;
|
||||||
//using lib::Thread;
|
//using lib::Thread;
|
||||||
|
using util::join;
|
||||||
|
|
||||||
namespace vault {
|
namespace vault {
|
||||||
namespace mem {
|
namespace mem {
|
||||||
|
|
@ -117,7 +121,7 @@ namespace test {
|
||||||
pool.add (MEM3, SIZ30);
|
pool.add (MEM3, SIZ30);
|
||||||
CHECK (pool.canServe(SIZ20));
|
CHECK (pool.canServe(SIZ20));
|
||||||
CHECK (pool.canServe(SIZ10));
|
CHECK (pool.canServe(SIZ10));
|
||||||
CHECK (pool.size() == 3);
|
CHECK (watch(pool).size() == 3);
|
||||||
CHECK (watch(pool).cnt(SIZ10) == 1);
|
CHECK (watch(pool).cnt(SIZ10) == 1);
|
||||||
CHECK (watch(pool).cnt(SIZ20) == 1);
|
CHECK (watch(pool).cnt(SIZ20) == 1);
|
||||||
CHECK (watch(pool).cnt(SIZ30) == 1);
|
CHECK (watch(pool).cnt(SIZ30) == 1);
|
||||||
|
|
@ -130,10 +134,23 @@ namespace test {
|
||||||
auto [m2,s2] = pool.retrieve (SIZ10);
|
auto [m2,s2] = pool.retrieve (SIZ10);
|
||||||
CHECK (SIZ10 == s2);
|
CHECK (SIZ10 == s2);
|
||||||
CHECK (MEM1 == m2);
|
CHECK (MEM1 == m2);
|
||||||
|
CHECK (watch(pool).size() == 3);
|
||||||
CHECK (watch(pool).cntFree() == 1);
|
CHECK (watch(pool).cntFree() == 1);
|
||||||
CHECK (pool.canServe(SIZ10));
|
CHECK (pool.canServe(SIZ10));
|
||||||
CHECK (pool.canServe(SIZ20));
|
CHECK (pool.canServe(SIZ20));
|
||||||
CHECK (pool.canServe(SIZ30));
|
CHECK (pool.canServe(SIZ30));
|
||||||
|
|
||||||
|
pool.reAdd (m2);
|
||||||
|
pool.reAdd (m1);
|
||||||
|
CHECK (watch(pool).cntFree() == 3);
|
||||||
|
|
||||||
|
lib::IterQueue<size_t> returned;
|
||||||
|
uint cnt = pool.cleanup(0.5
|
||||||
|
,[&](auto, size_t siz){ returned.feed (siz);});
|
||||||
|
CHECK (cnt == 1);
|
||||||
|
CHECK (join(returned) == "30"_expect);
|
||||||
|
CHECK (watch(pool).cntFree() == 2);
|
||||||
|
CHECK (watch(pool).size() == 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -145909,7 +145909,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
||||||
</node>
|
</node>
|
||||||
</node>
|
</node>
|
||||||
</node>
|
</node>
|
||||||
<node CREATED="1777052358645" ID="ID_1367907395" MODIFIED="1777052396869" TEXT="zum Aufräumen wird ein Empfänger-λ übergeben"/>
|
<node COLOR="#081963" CREATED="1777052358645" ID="ID_1367907395" MODIFIED="1777245268291" TEXT="zum Aufräumen wird ein Empfänger-λ übergeben"/>
|
||||||
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#620dbd" CREATED="1777052497154" ID="ID_1906132363" MODIFIED="1777084874533" 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"/>
|
<icon BUILTIN="yes"/>
|
||||||
</node>
|
</node>
|
||||||
|
|
@ -145985,6 +145985,90 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
||||||
<node CREATED="1777164528024" ID="ID_1047876896" MODIFIED="1777164541639" TEXT="dieser gewählte Buffer bekommt dann einen Score-Update"/>
|
<node CREATED="1777164528024" ID="ID_1047876896" MODIFIED="1777164541639" TEXT="dieser gewählte Buffer bekommt dann einen Score-Update"/>
|
||||||
</node>
|
</node>
|
||||||
</node>
|
</node>
|
||||||
|
<node COLOR="#338800" CREATED="1777206625753" ID="ID_1782953860" MODIFIED="1777245252573" TEXT="Vor-Reservierung implementieren">
|
||||||
|
<icon BUILTIN="button_ok"/>
|
||||||
|
<node CREATED="1777206637762" ID="ID_1447848991" MODIFIED="1777206704359" TEXT="Problem hier: wir sollten gute Matches bevorzugen">
|
||||||
|
<richcontent TYPE="NOTE"><html>
|
||||||
|
<head>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>
|
||||||
|
sonst besteht die Gefahr, sehr große Blöcke für kleine Anforderungen zu verschwenden und dann nachfolgend größere Allokationen anforern zu müssen, obwohl eigentlich genügend Storage lokal schon da wäre
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
</richcontent>
|
||||||
|
</node>
|
||||||
|
<node CREATED="1777206710869" ID="ID_1181820372" MODIFIED="1777230701643">
|
||||||
|
<richcontent TYPE="NODE"><html>
|
||||||
|
<head>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>
|
||||||
|
Schwierige Entscheidung: <b>Laufzeit-Trend ist nicht bekannt</b>
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
</richcontent>
|
||||||
|
<richcontent TYPE="NOTE"><html>
|
||||||
|
<head>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>
|
||||||
|
Insofern weiß man nicht einmal, ob der Aufwand hier bedeutsam ist, oder man einfach einen Vector sortieren könnte
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
</richcontent>
|
||||||
|
<icon BUILTIN="messagebox_warning"/>
|
||||||
|
</node>
|
||||||
|
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1777206798288" ID="ID_226587276" MODIFIED="1777230690484" TEXT="mögliche Ansätze">
|
||||||
|
<icon BUILTIN="edit"/>
|
||||||
|
<icon BUILTIN="help"/>
|
||||||
|
<node CREATED="1777206894812" ID="ID_1484542373" MODIFIED="1777206896343" TEXT="Pass-1 gute Matches"/>
|
||||||
|
<node CREATED="1777206897340" ID="ID_623647353" MODIFIED="1777207043059" TEXT="dann Pass-2 erst mal Kandidaten in einen Vector sammeln">
|
||||||
|
<richcontent TYPE="NOTE"><html>
|
||||||
|
<head>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>
|
||||||
|
Das Problem hierbei ist, daß wir dafür bereits Aufwand treiben müssen, der u.U den Aufwand für das Sortieren übersteigt
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
</richcontent>
|
||||||
|
</node>
|
||||||
|
<node CREATED="1777206924680" ID="ID_849247711" MODIFIED="1777207115135" TEXT="oder bei kleiner Zahl fehlender Allokationen diese einfach nicht reservieren">
|
||||||
|
<richcontent TYPE="NOTE"><html>
|
||||||
|
<head>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>
|
||||||
|
Da wir letztlich sowiso einen Pool-Allokator haben, kann es durchaus sinnvoll sein, neue Allokationen zu holen, und die unpassenden alten Allokationen zeitnah aber asynchron zum Pool zurückzuschicken
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
</richcontent>
|
||||||
|
</node>
|
||||||
|
<node CREATED="1777207118494" ID="ID_979090781" MODIFIED="1777207124089" TEXT="Hybrid-Lösung">
|
||||||
|
<node CREATED="1777207132147" ID="ID_229232835" MODIFIED="1777207150800" TEXT="sofort ein kleines Array auf dem Stack bereitlegen"/>
|
||||||
|
<node CREATED="1777207151639" ID="ID_423296164" MODIFIED="1777207164964" TEXT="im ersten Paß in drei Kategorien aufteilen">
|
||||||
|
<node CREATED="1777207166712" ID="ID_6555170" MODIFIED="1777207175786" TEXT="gute Matches"/>
|
||||||
|
<node CREATED="1777207176590" ID="ID_864575958" MODIFIED="1777207184966" TEXT="mäßige Matches"/>
|
||||||
|
<node CREATED="1777207185766" ID="ID_1169920501" MODIFIED="1777207190601" TEXT="schlechte Matches"/>
|
||||||
|
</node>
|
||||||
|
<node CREATED="1777207242030" ID="ID_1071090391" MODIFIED="1777207263436" TEXT="die mäßigen Matches nur sortieren, wenn es deutlich mehr als angefordert sind"/>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
<node BACKGROUND_COLOR="#c8c0b6" COLOR="#435e98" CREATED="1777230638540" ID="ID_905617278" MODIFIED="1777230684307" TEXT="wähle KISS ⟹ ein pass mit »guten« Matches und den Rest ignorieren">
|
||||||
|
<icon BUILTIN="yes"/>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
</node>
|
</node>
|
||||||
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1777050457640" ID="ID_55967690" MODIFIED="1777084249904" TEXT="Dokumentieren per Test">
|
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1777050457640" ID="ID_55967690" MODIFIED="1777084249904" TEXT="Dokumentieren per Test">
|
||||||
<icon BUILTIN="pencil"/>
|
<icon BUILTIN="pencil"/>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue