diff --git a/src/vault/mem/engine-buffer-allocator.hpp b/src/vault/mem/engine-buffer-allocator.hpp index 9d3212daa..346469ade 100644 --- a/src/vault/mem/engine-buffer-allocator.hpp +++ b/src/vault/mem/engine-buffer-allocator.hpp @@ -13,12 +13,11 @@ /** @file engine-buffer-allocator.hpp ** Implementation of render buffer allocation for the Lumiera Engine. - ** Instantiated as global service and part of the Engine, this global - ** allocation coordinator is responsible to provide pre-allocated blocks - ** for render buffers. These allocations are categorised by their size, - ** and will be sent down to LocalMemPool instances running in each - ** worker thread. This involves asynchronous messaging to avoid - ** any blocking of work jobs due to central resource management. + ** Memory for render buffer is allocated in blocks with a size aligned + ** to the typical cache-line granularity. These allocations are maintained + ** in thread-local pools and re-used to the extent possible. Excess blocks + ** are sent back and collected by the EngineBufferManager, which serves + ** thus as an background collector and manager for the buffer memory. ** ** @todo WIP-WIP 4/2026 The intention is to use a family of pools eventually, ** with some of the most relevant tile sizes. The EngineBufferManager @@ -46,7 +45,7 @@ #include "lib/util-quant.hpp" #include "lib/allocator-handle.hpp" //#include "lib/hash-value.h" -#include "vault/mem/buffhandle.hpp" +#include "vault/mem/engine-buffer-manager.hpp" //#include "vault/mem/type-handler.hpp" //#include "vault/mem/buffer-local-tag.hpp" #include "lib/nocopy.hpp" @@ -93,12 +92,6 @@ namespace mem { static_assert (lib::allo::is_Stateless_v); - struct Alloc - { - Buff* mem; - size_t siz; - }; - static constexpr size_t numTiles (size_t sizRequest) { diff --git a/src/vault/mem/engine-buffer-manager.cpp b/src/vault/mem/engine-buffer-manager.cpp index f387cfff7..25f1f2b43 100644 --- a/src/vault/mem/engine-buffer-manager.cpp +++ b/src/vault/mem/engine-buffer-manager.cpp @@ -15,29 +15,17 @@ /** @file engine-buffer-manager.cpp ** Implementation of render buffer allocation for the Lumiera Engine. ** Instantiated as global service and part of the Engine, this global - ** allocation coordinator is responsible to provide pre-allocated blocks - ** for render buffers. These allocations are categorised by their size, - ** and will be sent down to LocalMemPool instances running in each - ** worker thread. This involves asynchronous messaging to avoid - ** any blocking of work jobs due to central resource management. - ** - ** @todo WIP-WIP 4/2026 The intention is to use a family of pools eventually, - ** with some of the most relevant tile sizes. The EngineBufferManager - ** would then actually retain ownership of all allocations and just - ** _lease_ them to the LocalMemPool instances. Allocations would be - ** added in extents, likely also using some usage bitmap. However - ** building any such kind of optimised allocator requires to conduct - ** very detailed _and_ realistic performance observations; furthermore - ** it is likely that we'd need some dynamic scheme to select suitable - ** tile sizes and pool dimensions. Not sure if it is even possible - ** to improve performance by such a scheme; with some likelihood - ** the majority of the benefits will be reaped already just by - ** introducing thread-local pools without wasting too much memory. + ** buffer coordinator uses the [actual allocator](\ref engine-buffer-allocator.hpp) + ** internally to maintain a pool of usage buffer memory blocks. Furthermore, + ** both a synchronous and an asynchronous interface is provided to request + ** allocations, that will be sent as \ref Alloc entries to some local memory + ** pool, that is an \ref AllocReceiver, thereby passing control over these + ** blocks of memory. The contract is that the local memory user has to + ** return those blocks reliably to the EngineBuffrerManager, which + ** itself is also an \ref AllocReceiver. ** ** @see EnginePufferManager_test ** @see LocalBufferStore_test - ** @see BufferProviderProtocol_test - ** @see buffer-provider.hpp */ @@ -82,6 +70,18 @@ namespace mem { /* === EngineBufferManager implementation === */ + void + EngineBufferManager::handleAllocRequests() + { + requestQueue_.consume_all ([this](AllocRequest request) + { + REQUIRE (request.receiver); + BufferAllocator allocator; + Alloc alloc = allocator.allocate (request.sizRequest); + ENSURE (alloc.mem); + request.receiver->supply (alloc); + }); + } }} // namespace vault::mem diff --git a/src/vault/mem/engine-buffer-manager.hpp b/src/vault/mem/engine-buffer-manager.hpp index c63819cd0..96ccb05be 100644 --- a/src/vault/mem/engine-buffer-manager.hpp +++ b/src/vault/mem/engine-buffer-manager.hpp @@ -20,18 +20,15 @@ ** worker thread. This involves asynchronous messaging to avoid ** any blocking of work jobs due to central resource management. ** - ** @todo WIP-WIP 4/2026 The intention is to use a family of pools eventually, - ** with some of the most relevant tile sizes. The EngineBufferManager - ** would then actually retain ownership of all allocations and just - ** _lease_ them to the LocalMemPool instances. Allocations would be - ** added in extents, likely also using some usage bitmap. However - ** building any such kind of optimised allocator requires to conduct - ** very detailed _and_ realistic performance observations; furthermore - ** it is likely that we'd need some dynamic scheme to select suitable - ** tile sizes and pool dimensions. Not sure if it is even possible - ** to improve performance by such a scheme; with some likelihood - ** the majority of the benefits will be reaped already just by - ** introducing thread-local pools without wasting too much memory. + ** Besides the interface of the EngineBufferManager, this header also + ** defines some common structures, like the \ref mem::Alloc, and the + ** baseclass \ref AllocReceiver, with a lock-free inbound queue + ** so that allocations can be supplied to a local memory manager + ** and picked up from within the worker thread later. + ** @remark both the LocalMemPool (for each worker thread) and the + ** EngineBufferManager, as global memory service, are based on + ** such an input queue, allowing to transfer control over some + ** allocation from the central allocator to a local memory pool. ** ** @see EnginePufferManager_test ** @see LocalBufferStore_test @@ -50,6 +47,7 @@ #include "lib/nocopy.hpp" //#include "lib/util.hpp" +#include namespace vault{ @@ -64,6 +62,8 @@ namespace mem { namespace { // config and internal helpers... + const size_t INQUEUE_SIZ = 30; ///< initial size of the lock-free provision queue + } struct Alloc @@ -73,14 +73,60 @@ namespace mem { }; + /** + * Base building block: a memory handler + * that can receive memory allocations for further use. + */ + class AllocReceiver + : util::NonCopyable + { + protected: + using InQueue = boost::lockfree::queue; + InQueue inQueue_; + + AllocReceiver() + : inQueue_{INQUEUE_SIZ} + { } + + public: + /** Transfer control over the given allocation. + * @remark the \ref Alloc is placed into a lock-free queue; + * implementation code from a subclass will have to + * receive and dispatch those entries */ + void + supply (Alloc const& alloc) + { + inQueue_.push (alloc); + } + }; + + + /*********************************************************//** * Core service of the Render Engine : global buffer manager. */ class EngineBufferManager - : util::NonCopyable + : public AllocReceiver { + struct AllocRequest + { + AllocReceiver* receiver; + size_t sizRequest; + }; + + using RequestQueue = boost::lockfree::queue; + RequestQueue requestQueue_; + public: + EngineBufferManager() + : AllocReceiver{} + , requestQueue_{INQUEUE_SIZ} + { } + + private: + /** process all pending requests in the queue */ + void handleAllocRequests(); }; diff --git a/src/vault/mem/local-mem-pool.hpp b/src/vault/mem/local-mem-pool.hpp index 99874dac8..5bd4643f7 100644 --- a/src/vault/mem/local-mem-pool.hpp +++ b/src/vault/mem/local-mem-pool.hpp @@ -75,10 +75,8 @@ #include "lib/error.hpp" #include "vault/mem/engine-buffer-manager.hpp" -#include "lib/nocopy.hpp" #include "lib/util.hpp" -#include #include #include #include @@ -91,8 +89,6 @@ namespace mem { namespace { // Configuration tuning parameters - 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 MISFIT_PENALTY = 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 @@ -126,6 +122,7 @@ namespace mem { * for use in a massive multithreaded environment. */ class LocalMemPool + : public AllocReceiver { struct Block { @@ -140,17 +137,15 @@ namespace mem { { } }; - using InQueue = boost::lockfree::queue; using BlockList = std::list; - InQueue inQueue_; BlockList blocks_; int32_t maxScore_{1}; public: LocalMemPool() - : inQueue_{INQUEUE_SIZ} + : AllocReceiver{} , blocks_{} { } @@ -232,7 +227,7 @@ namespace mem { void supply (Buff* mem, size_t siz) { - inQueue_.push ({mem,siz}); + AllocReceiver::supply ({mem,siz}); } /** diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 51eb5d3e1..5e9fd4bc0 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -146571,6 +146571,92 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) + + + + + + +

+ das ist zwar nur ein Grenzfall +

+

+  — aber er stellt das gesamte Konzept in Frage +

+ + +
+ + + + + +

+ Es handelt sich ganz klassisch um einen »Race« — das heißt, diese Situation ist in keinster Weise regulär vorgesehen, und dürfrte auch auch schwierig zu reproduzieren sein; sie sollte daher nicht der Maßstab sein, an dem das Konstrukt insgeseamt ausgerichtet wird. Leider aber führt diese Situation zur memory corruption, wenn wir das bisher ausgearbeitete Konzept umsetzen +

+ + +
+ + + + + + +

+ denn andernfalls könnte man versuchen, über eine Spezialbehandlung im Destruktor die gesendete Speicher-Anforderung irgendwie stornieren (wiewohl auch dabei noch ein Race auftreten könnte) +

+ + +
+
+ + + + + + +

+ So ähnlich wie der ZombieCheck... +

+ + +
+
+ + + + + + +

+ Erläuterung... +

+
    +
  • + der Callback ruft direkt oder indirekt eine Funktion auf, die den Speicher entgegennimmt +
  • +
  • + dieser Funktion wird eine "self"-Referenz übergeben, die aber zum Zeitpunkt des Aufrufes bereits nicht mehr valide sein könnte +
  • +
  • + solange nur der Code läuft, und auf Parameter und statische Variable zugreift, ist diese Operation selbst dann noch fehlerfrei ausführbar, wenn die self-Referenz ungültig ist +
  • +
  • + aber da die Queue an der self-Referenz hängt, würde beim ersten Zugriff bereits anderweitig verwendeter Speicher referenziert. +
  • +
  • + man müßte also irgend eine Art von ID erzeugen, über die sich feststellen läßt, ob die self-Referenz noch gültig ist, beispielsweise als Eintrag in einer statischen Hashtable +
  • +
  • + einfach ist das nicht, da auch dieser Check selber wieder threadsafe sein muß (allerdings ist der relevante Codepfad weder kritisch, noch strongly contended) +
  • +
+ + +
+
+
@@ -171636,8 +171722,8 @@ std::cout << tmpl.render({"what", "World"}) << s - +