BufferProvider: decide how to introduce locking
I prefer to favour a small number of locks at API level. See https://issues.lumiera.org/ticket/1429#comment:1
This commit is contained in:
parent
5a47b099be
commit
76542e873a
4 changed files with 135 additions and 26 deletions
|
|
@ -71,9 +71,17 @@ namespace mem {
|
|||
|
||||
/* === EngineBufferManager implementation === */
|
||||
|
||||
/**
|
||||
* @note 5/2026 I prefer a small number of locks at API level,
|
||||
* based on the assumption that this processing environment
|
||||
* does not exhibit much contention. This should be [validated] however.
|
||||
* [validated]: https://issues.lumiera.org/ticket/1429#comment:1
|
||||
*/
|
||||
void
|
||||
EngineBufferManager::handleAllocRequests()
|
||||
EngineBufferManager::processPendingRequests()
|
||||
{
|
||||
Lock sync{this};
|
||||
inQueue_ .consume_all ([this](Alloc alloc){ doRedeemAllocation (alloc); });
|
||||
requestQueue_.consume_all ([this](AllocRequest request)
|
||||
{
|
||||
REQUIRE (request.receiver);
|
||||
|
|
@ -87,6 +95,7 @@ namespace mem {
|
|||
Alloc
|
||||
EngineBufferManager::requestAllocation (size_t sizRequest)
|
||||
{
|
||||
Lock sync{this};
|
||||
processPendingRequests();
|
||||
return doPerformAllocation (sizRequest);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@
|
|||
//#include "vault/mem/type-handler.hpp"
|
||||
//#include "vault/mem/buffer-local-tag.hpp"
|
||||
#include "lib/nocopy.hpp"
|
||||
#include "lib/sync.hpp"
|
||||
//#include "lib/util.hpp"
|
||||
|
||||
#include <boost/lockfree/queue.hpp>
|
||||
|
|
@ -66,6 +67,9 @@ namespace mem {
|
|||
|
||||
}
|
||||
|
||||
using lib::Sync;
|
||||
using lib::RecursiveLock_NoWait;
|
||||
|
||||
struct Alloc
|
||||
{
|
||||
Buff* mem;
|
||||
|
|
@ -110,6 +114,7 @@ namespace mem {
|
|||
*/
|
||||
class EngineBufferManager
|
||||
: public AllocReceiver
|
||||
, public Sync<RecursiveLock_NoWait>
|
||||
{
|
||||
struct AllocRequest
|
||||
{
|
||||
|
|
@ -133,7 +138,7 @@ namespace mem {
|
|||
|
||||
|
||||
size_t size() const;
|
||||
bool empty() const;
|
||||
bool empty() const;
|
||||
|
||||
|
||||
/****************************************************************//**
|
||||
|
|
@ -166,9 +171,6 @@ namespace mem {
|
|||
void processPendingRequests();
|
||||
|
||||
private:
|
||||
/** process all pending requests in the queue */
|
||||
void handleAllocRequests();
|
||||
|
||||
/** performs the actual allocation work */
|
||||
Alloc doPerformAllocation (size_t sizRequest);
|
||||
void doRedeemAllocation (Alloc);
|
||||
|
|
@ -184,13 +186,6 @@ namespace mem {
|
|||
|
||||
}
|
||||
|
||||
inline void
|
||||
EngineBufferManager::processPendingRequests()
|
||||
{
|
||||
inQueue_.consume_all ([this](Alloc alloc){ doRedeemAllocation (alloc); });
|
||||
handleAllocRequests();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** wrapper to inspect internals from a unit test */
|
||||
|
|
@ -219,13 +214,19 @@ namespace mem {
|
|||
size_t
|
||||
bytesAllocd()
|
||||
{
|
||||
UNIMPLEMENTED ("alloc state diagnostic");
|
||||
return globalPool_.allocated_;
|
||||
}
|
||||
|
||||
size_t
|
||||
bytesLeased()
|
||||
{
|
||||
UNIMPLEMENTED ("alloc state diagnostic");
|
||||
return globalPool_.leased_;
|
||||
}
|
||||
|
||||
size_t
|
||||
numAllocs()
|
||||
{
|
||||
return globalPool_.cnt_;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1430 : add pool diagnostics here if we actually switch to pooled allocations
|
||||
|
|
|
|||
|
|
@ -111,27 +111,55 @@ namespace test {
|
|||
|
||||
|
||||
|
||||
/** @test show that a direct request for allocation is honored */
|
||||
/** @test show that a direct request for allocation is honored
|
||||
* - request some memory
|
||||
* - read and write to that memory
|
||||
* - return all leased allocations
|
||||
* - verify the diagnostic meshes up with the allocation
|
||||
* @remark in principle it is not possible to prove that some
|
||||
* memory allocation actually works, other than checking
|
||||
* that access to the given memory is possible, because
|
||||
* the fact that no memory corruption took place is
|
||||
* only semi-decidable (you can show a corruption event,
|
||||
* but not the absence of any such an event).
|
||||
*/
|
||||
void
|
||||
verify_syncRequest()
|
||||
{
|
||||
EngineBufferManager manager;
|
||||
SHOW_EXPR(watch(manager).isEmpty())
|
||||
CHECK (watch(manager).isEmpty());
|
||||
|
||||
Alloc alloc = manager.requestAllocation (555);
|
||||
CHECK (not alloc.empty());
|
||||
CHECK (alloc.siz >= 555);
|
||||
|
||||
SHOW_EXPR(watch(manager).bytesAllocd())
|
||||
SHOW_EXPR(watch(manager).bytesLeased())
|
||||
CHECK (alloc.siz == watch(manager).bytesAllocd());
|
||||
CHECK (alloc.siz == watch(manager).bytesLeased());
|
||||
CHECK (alloc.siz % HeapBufferAllocator::TILE_SIZ == 0);
|
||||
|
||||
// can use that memory
|
||||
char* buf = reinterpret_cast<char*> (alloc.mem);
|
||||
string ranS{lib::randStr(555-1)};
|
||||
auto* p = std::copy (ranS.begin(), ranS.end(), buf);
|
||||
SHOW_EXPR(p - buf)
|
||||
CHECK (ranS.length() == size_t(p - buf));
|
||||
*p = '\0';
|
||||
CHECK (ranS == buf);
|
||||
|
||||
CHECK (1 == watch(manager).numAllocs());
|
||||
Alloc a2 = manager.requestAllocation (1'000'000);
|
||||
CHECK (not a2.empty());
|
||||
CHECK (a2.siz >= 1e6);
|
||||
CHECK (2 == watch(manager).numAllocs());
|
||||
CHECK (1e6+555 < watch(manager).bytesLeased());
|
||||
|
||||
manager.supply (a2);
|
||||
manager.processPendingRequests();
|
||||
CHECK (1 == watch(manager).numAllocs());
|
||||
CHECK (1e6 > watch(manager).bytesLeased());
|
||||
manager.supply (alloc);
|
||||
manager.processPendingRequests();
|
||||
CHECK (0 == watch(manager).numAllocs());
|
||||
CHECK (0 == watch(manager).bytesLeased());
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -146736,7 +146736,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
<icon BUILTIN="messagebox_warning"/>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#f0d5c5" COLOR="#990033" CREATED="1778507988939" ID="ID_48400467" MODIFIED="1778508004878" TEXT="Klären: wer bearbeitet angeforderte Allokationen?">
|
||||
<node COLOR="#435e98" CREATED="1778507988939" ID="ID_48400467" MODIFIED="1778635053866" TEXT="Klären: wer bearbeitet angeforderte Allokationen?">
|
||||
<icon BUILTIN="help"/>
|
||||
<node CREATED="1778508010370" ID="ID_681344982" MODIFIED="1778508200989" TEXT="Fallback-Lösung: der Anforderer selbst (synchron)">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
|
|
@ -146761,7 +146761,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
</body>
|
||||
</html></richcontent>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#e2dbb4" CREATED="1778592973322" ID="ID_1723782536" MODIFIED="1778593812308" TEXT="hybrid-Lösung: der synchrone Fall schiebt den asynchronen Fall">
|
||||
<node BACKGROUND_COLOR="#e2dbb4" CREATED="1778592973322" ID="ID_1723782536" MODIFIED="1778635048385" TEXT="hybrid-Lösung: der synchrone Fall schiebt den asynchronen Fall">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head/>
|
||||
<body>
|
||||
|
|
@ -146771,6 +146771,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
</body>
|
||||
</html></richcontent>
|
||||
<arrowlink COLOR="#e7f3c5" DESTINATION="ID_1339362365" ENDARROW="Default" ENDINCLINATION="273;14;" ID="Arrow_ID_74341588" STARTARROW="None" STARTINCLINATION="226;-11;"/>
|
||||
<linktarget COLOR="#cffd68" DESTINATION="ID_1723782536" ENDARROW="Default" ENDINCLINATION="2031;68;" ID="Arrow_ID_1674470149" SOURCE="ID_226540112" STARTARROW="None" STARTINCLINATION="1004;42;"/>
|
||||
<icon BUILTIN="forward"/>
|
||||
</node>
|
||||
<node CREATED="1778508357967" ID="ID_1467114460" MODIFIED="1778508767506" TEXT="naheliegende Lösung: Hintergrundthread">
|
||||
|
|
@ -146814,6 +146815,58 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
<arrowlink COLOR="#ff1b15" DESTINATION="ID_931645192" ENDARROW="Default" ENDINCLINATION="-857;174;" ID="Arrow_ID_801625969" STARTARROW="None" STARTINCLINATION="171;-11;"/>
|
||||
<icon BUILTIN="clanbomber"/>
|
||||
</node>
|
||||
<node CREATED="1778630840458" ID="ID_108123775" MODIFIED="1778630844917" TEXT="Locking">
|
||||
<node CREATED="1778630852336" ID="ID_1688369877" MODIFIED="1778630859835" TEXT="zwei mögliche Ansätze">
|
||||
<node CREATED="1778630861078" ID="ID_692489731" MODIFIED="1778630977002" TEXT="möglichst kurz/lokal">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
im Grunde müßte man nur den Zugang zum Memory-Manager selber locken, denn die ganzen Queue-Operationen sind ja schon lock-free und threadsafe; auch könnten diese Locks dann non-recursive sein
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
</node>
|
||||
<node CREATED="1778630869382" ID="ID_1688710426" MODIFIED="1778631446175" TEXT="möglichst wenig Locks">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
...andererseits ist jeder (Mutex)-Lock ein Kernel-Aufruf, und wir erwarten durchaus ganze Bündel an Requests. Außerdem wird mit wenigen Locks auf API-Level viel klarer, daß das Locking vollständig, und der Zustand konsistent ist.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#ccb59b" COLOR="#6e2a38" CREATED="1778631447430" ID="ID_1196517656" MODIFIED="1778631809891" TEXT="entscheide mich (nach Bauchgefühl) für »wenig Locks«">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
...und zwar sagt mir mein Bauchgefühl, daß wir hier keine Situation mit starker Contention haben; das hat sich auch bereits im Zusammenhang mit dem Scheduler gezeigt. Im Regelfall erwarte ich, daß die Anfragen durch die Queue gehen, und damit ohne Interferenz aneinander vorbeikommen. Wenn dann tatsächlich mal ein Thread den synchronen Pfad einschlägt, erscheint es mir sinnvoller, alle Arbeit in einem Schwung zu erledigen, selbst wenn dadruch (ohenhin nur ausnahmsweise!) ein anderer Thread mal geblockt wird. Letztlich gilt hier immer: die Jobs sind <i>vergleichsweise groß. </i>
|
||||
</p>
|
||||
<p>
|
||||
|
||||
</p>
|
||||
<p>
|
||||
Den Ausschlag gegeben hat aber eine andere Hinsicht: Klarheit und Wartbarkeit sind wichtiger, spezielle Performance-Tweaks sollte man empirisch beweisen.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
<font ITALIC="true" NAME="SansSerif" SIZE="14"/>
|
||||
<icon BUILTIN="yes"/>
|
||||
</node>
|
||||
<node CREATED="1778631791231" ID="ID_118677112" MODIFIED="1778631843980" TEXT="in #1429 notiert"/>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1777324560690" ID="ID_318881353" MODIFIED="1778507667074" TEXT="Rahmen per Test abstecken">
|
||||
<icon BUILTIN="pencil"/>
|
||||
|
|
@ -146831,11 +146884,10 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
</html></richcontent>
|
||||
<linktarget COLOR="#47c9c9" DESTINATION="ID_1644044904" ENDARROW="Default" ENDINCLINATION="6;-56;" ID="Arrow_ID_1619156849" SOURCE="ID_1470622255" STARTARROW="None" STARTINCLINATION="-20;33;"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1778595475430" ID="ID_992537338" MODIFIED="1778597417344" STYLE="fork" TEXT="Diagnose-Zugang schaffen: watch(manager)">
|
||||
<icon BUILTIN="pencil"/>
|
||||
<node COLOR="#338800" CREATED="1778595475430" ID="ID_992537338" MODIFIED="1778635094590" TEXT="Diagnose-Zugang schaffen: watch(manager)">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1778599970816" ID="ID_346337478" MODIFIED="1778599973845" TEXT="verify_syncRequest">
|
||||
<icon BUILTIN="pencil"/>
|
||||
<node COLOR="#338800" CREATED="1778599970816" ID="ID_346337478" MODIFIED="1778635102267" TEXT="verify_syncRequest">
|
||||
<node CREATED="1778599985224" ID="ID_1402142899" MODIFIED="1778599992550" TEXT="eine Allokation machen"/>
|
||||
<node CREATED="1778599993498" ID="ID_1315033221" MODIFIED="1778600012854" TEXT="Diagnose der belegten Bytes checken"/>
|
||||
<node CREATED="1778600013692" ID="ID_245317435" MODIFIED="1778600031040" TEXT="was in den Speicher schreiben">
|
||||
|
|
@ -146858,6 +146910,9 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
</html></richcontent>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1778635069297" ID="ID_1942014687" MODIFIED="1778635075164" TEXT="mehrere Allokationen machen"/>
|
||||
<node CREATED="1778635075937" ID="ID_1005055901" MODIFIED="1778635080660" TEXT="alles wieder freigeben"/>
|
||||
<node CREATED="1778635083791" ID="ID_227876159" MODIFIED="1778635088455" TEXT="Diagnostik prüfen"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#d2beaf" COLOR="#5c4d6e" CREATED="1778599975152" ID="ID_1790536650" MODIFIED="1778599980968" TEXT="verify_asyncRequest">
|
||||
<icon BUILTIN="hourglass"/>
|
||||
|
|
@ -171564,7 +171619,7 @@ std::cout << tmpl.render({"what", "World"}) << s
|
|||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1778547854514" HGAP="36" ID="ID_1695116908" LINK="https://issues.lumiera.org/ticket/1429" MODIFIED="1778547956862" TEXT="#1429 control effort for global buffer allocator " VSHIFT="10">
|
||||
<icon BUILTIN="flag-yellow"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#f0d5c5" COLOR="#990033" CREATED="1778547971007" ID="ID_1189474796" MODIFIED="1778548102342">
|
||||
<node BACKGROUND_COLOR="#f0d5c5" COLOR="#990033" CREATED="1778547971007" ID="ID_1189474796" MODIFIED="1778634786640">
|
||||
<richcontent TYPE="NODE"><html>
|
||||
<head/>
|
||||
<body>
|
||||
|
|
@ -171583,6 +171638,22 @@ std::cout << tmpl.render({"what", "World"}) << s
|
|||
</html></richcontent>
|
||||
<font NAME="SansSerif" SIZE="11"/>
|
||||
<icon BUILTIN="messagebox_warning"/>
|
||||
<node COLOR="#435e98" CREATED="1778634707713" HGAP="30" ID="ID_226540112" MODIFIED="1778635048385" TEXT="habe aber inzwischen eine elegante Lösung für dieses Dilemma gefunden" VSHIFT="19">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
...nämlich indem der Job letztlich auf den synchronen Fall zurückgreift, dieser aber durch die gleiche Mechanik implementiert ist, wie der asynchrone Fall, und dabei gleich alle aufgesammelten Requests in den Queues mit verarbeitet. Dadruch wird effektiv ein Batch an Allokator-Aufwand in denjenigen Worker geschoben, der rein zufällig als erster auf eine nicht bediente Allokation stößt. Das Schöne an dieser Code-Struktur ist, daß die Möglichkeit offen bleibt, später zur Performance-Optimierung (oder nur in bestimmten Situationen) dennoch etwas speziell einzurichten / schedulen / auszuführen, ohne daß dazu der Code im EngineBufferManager geändert werden muß
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
<arrowlink COLOR="#cffd68" DESTINATION="ID_1723782536" ENDARROW="Default" ENDINCLINATION="2031;68;" ID="Arrow_ID_1674470149" STARTARROW="None" STARTINCLINATION="1004;42;"/>
|
||||
<font NAME="SansSerif" SIZE="11"/>
|
||||
<icon BUILTIN="idea"/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
|
|
|
|||
Loading…
Reference in a new issue