diff --git a/src/lib/allocator-handle.hpp b/src/lib/allocator-handle.hpp index d7c0477f3..4676bef02 100644 --- a/src/lib/allocator-handle.hpp +++ b/src/lib/allocator-handle.hpp @@ -51,6 +51,7 @@ #include #include +#include #include diff --git a/src/vault/mem/engine-buffer-allocator.hpp b/src/vault/mem/engine-buffer-allocator.hpp new file mode 100644 index 000000000..9d3212daa --- /dev/null +++ b/src/vault/mem/engine-buffer-allocator.hpp @@ -0,0 +1,153 @@ +/* + ENGINE-BUFFER-ALLOCATOR.hpp - global buffer allocation scheme + + Copyright (C) + 2026, Hermann Vosseler + +  **Lumiera** is free software; you can redistribute it and/or modify it +  under the terms of the GNU General Public License as published by the +  Free Software Foundation; either version 2 of the License, or (at your +  option) any later version. See the file COPYING for further details. + +*/ + +/** @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. + ** + ** @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. + ** + ** @see EnginePufferManager_test + ** @see LocalBufferStore_test + ** @see BufferProviderProtocol_test + ** @see buffer-provider.hpp + */ + +#ifndef VAULT_MEM_ENGINE_BUFFER_ALLOCATOR_H +#define VAULT_MEM_ENGINE_BUFFER_ALLOCATOR_H + +#include "lib/error.hpp" +#include "lib/util-quant.hpp" +#include "lib/allocator-handle.hpp" +//#include "lib/hash-value.h" +#include "vault/mem/buffhandle.hpp" +//#include "vault/mem/type-handler.hpp" +//#include "vault/mem/buffer-local-tag.hpp" +#include "lib/nocopy.hpp" +//#include "lib/util.hpp" + + + +namespace vault{ +namespace mem { + +// using lib::HashVal; +// using util::unConst; + +#include +#include + namespace err = lumiera::error; + + using std::byte; + + + namespace { // config and internal helpers... + + } + + + + /* === Setup of »naïve« buffer allocation === */ + + /***********************************************************//** + * A placeholder definition for the real buffer allocator: + * - allocates buffers aligned to a fixed #TILE_SIZ + * - passes any allocation request directly to the heap allocator + * - performs no state tracking and cleanup whatsoever. + */ + struct HeapBufferAllocator + { + static constexpr size_t TILE_SIZ = 64; + using BaseTile = std::array; + + using Allo = std::allocator; + using AlloT = std::allocator_traits; + + static_assert (util::isPow2 (sizeof(BaseTile))); + static_assert (lib::allo::is_Stateless_v); + + + struct Alloc + { + Buff* mem; + size_t siz; + }; + + static constexpr size_t + numTiles (size_t sizRequest) + { + return util::ceilDiv (sizRequest, sizeof(BaseTile)); + } + + static Buff* + mark_as_Buffer (void* mem) + { + return static_cast (mem); + } + + + /** + * Allocate continuous memory block large enough to fit. + * @throws std::bad_alloc when allocation fails + * @note client is responsible to \ref deallocate() + */ + Alloc + allocate (size_t sizRequest) + { + Allo allo; + auto cnt = numTiles (sizRequest); + auto loc = AlloT::allocate (allo, cnt); + return {mark_as_Buffer(loc) + ,cnt * TILE_SIZ + }; + } + + /** + * Deallocates a memory block that was previously + * [allocated](\ref allocate()) by this buffer allocator. + */ + void + deallocate (Alloc alloc) + { + Allo allo; + auto cnt = numTiles (alloc.siz); + auto mem = reinterpret_cast (alloc.mem); + AlloT::deallocate (allo, mem, cnt); + } + }; + + + + + + + + +}} // namespace vault::mem +#endif /*VAULT_MEM_ENGINE_BUFFER_ALLOCATOR_H*/ diff --git a/src/vault/mem/engine-buffer-manager.cpp b/src/vault/mem/engine-buffer-manager.cpp new file mode 100644 index 000000000..f387cfff7 --- /dev/null +++ b/src/vault/mem/engine-buffer-manager.cpp @@ -0,0 +1,87 @@ +/* + EnginePufferManager - central buffer allocation coordinator + + Copyright (C) + 2026, Hermann Vosseler + +  **Lumiera** is free software; you can redistribute it and/or modify it +  under the terms of the GNU General Public License as published by the +  Free Software Foundation; either version 2 of the License, or (at your +  option) any later version. See the file COPYING for further details. + +* *****************************************************************/ + + +/** @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. + ** + ** @see EnginePufferManager_test + ** @see LocalBufferStore_test + ** @see BufferProviderProtocol_test + ** @see buffer-provider.hpp + */ + + +#include "lib/error.hpp" +#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/engine-buffer-allocator.hpp" +//#include "vault/mem/buffer-local-tag.hpp" +#include "lib/nocopy.hpp" +//#include "lib/util.hpp" + +#include +#include + + +namespace vault{ +namespace mem { + +// using lib::HashVal; +// using util::unConst; + + namespace err = lumiera::error; + + using std::byte; + + + namespace { // config and internal helpers... + + } + + + + + + using BufferAllocator = HeapBufferAllocator; + + + + + /* === EngineBufferManager implementation === */ + + + +}} // namespace vault::mem diff --git a/src/vault/mem/engine-buffer-manager.hpp b/src/vault/mem/engine-buffer-manager.hpp index 520d0a225..c63819cd0 100644 --- a/src/vault/mem/engine-buffer-manager.hpp +++ b/src/vault/mem/engine-buffer-manager.hpp @@ -43,19 +43,13 @@ #define VAULT_MEM_ENGINE_BUFFR_MANAGA_H -#include "lib/error.hpp" -#include "lib/util-quant.hpp" -#include "lib/allocator-handle.hpp" //#include "lib/hash-value.h" -//#include "include/logging.h" #include "vault/mem/buffhandle.hpp" //#include "vault/mem/type-handler.hpp" //#include "vault/mem/buffer-local-tag.hpp" #include "lib/nocopy.hpp" //#include "lib/util.hpp" -#include -#include namespace vault{ @@ -66,96 +60,27 @@ namespace mem { namespace err = lumiera::error; - using std::byte; namespace { // config and internal helpers... } - - - /* === Setup of »naïve« buffer allocation === */ - - /** - * A placeholder definition for the real buffer allocator: - * - allocates buffers aligned to a fixed #TILE_SIZ - * - passes any allocation request directly to the heap allocator - * - performs no state tracking and cleanup whatsoever. - */ - struct HeapBufferAllocator + struct Alloc { - static constexpr size_t TILE_SIZ = 64; - using BaseTile = std::array; - - using Allo = std::allocator; - using AlloT = std::allocator_traits; - - static_assert (util::isPow2 (sizeof(BaseTile))); - static_assert (lib::allo::is_Stateless_v); - - - struct Alloc - { - Buff* mem; - size_t siz; - }; - - static constexpr size_t - numTiles (size_t sizRequest) - { - return util::ceilDiv (sizRequest, sizeof(BaseTile)); - } - - static Buff* - mark_as_Buffer (void* mem) - { - return static_cast (mem); - } - - - /** - * Allocate continuous memory block large enough to fit. - * @throws std::bad_alloc when allocation fails - * @note client is responsible to \ref deallocate() - */ - Alloc - allocate (size_t sizRequest) - { - Allo allo; - auto cnt = numTiles (sizRequest); - auto loc = AlloT::allocate (allo, cnt); - return {mark_as_Buffer(loc) - ,cnt * TILE_SIZ - }; - } - - /** - * Deallocates a memory block that was previously - * [allocated](\ref allocate()) by this buffer allocator. - */ - void - deallocate (Alloc alloc) - { - Allo allo; - auto cnt = numTiles (alloc.siz); - auto mem = reinterpret_cast (alloc.mem); - AlloT::deallocate (allo, mem, cnt); - } + Buff* mem; + size_t siz; }; - - - /*********************************************************//** * Core service of the Render Engine : global buffer manager. */ class EngineBufferManager + : util::NonCopyable { public: - using BufferAllocator = HeapBufferAllocator; }; diff --git a/src/vault/mem/local-buffer-store.hpp b/src/vault/mem/local-buffer-store.hpp index 0dc4c67eb..39806e1f8 100644 --- a/src/vault/mem/local-buffer-store.hpp +++ b/src/vault/mem/local-buffer-store.hpp @@ -25,7 +25,8 @@ #include "vault/mem/buffer-provider-setup.hpp" -#include "vault/mem/buffhandle.hpp" ///////////////TODO +#include "vault/mem/engine-buffer-manager.hpp" +#include "vault/mem/local-mem-pool.hpp" #include "lib/local-slice.hpp" #include "lib/depend.hpp" diff --git a/src/vault/mem/local-mem-pool.hpp b/src/vault/mem/local-mem-pool.hpp index d0e23369d..99874dac8 100644 --- a/src/vault/mem/local-mem-pool.hpp +++ b/src/vault/mem/local-mem-pool.hpp @@ -74,7 +74,7 @@ #include "lib/error.hpp" -#include "vault/mem/buffhandle.hpp" +#include "vault/mem/engine-buffer-manager.hpp" #include "lib/nocopy.hpp" #include "lib/util.hpp" @@ -127,12 +127,6 @@ namespace mem { */ class LocalMemPool { - struct Alloc - { - Buff* mem; - size_t siz; - }; - struct Block { Alloc alloc{nullptr,0}; @@ -236,7 +230,7 @@ namespace mem { * unless it is [yielded](\ref yield()) or disposed as part of clean-up. */ void - add (Buff* mem, size_t siz) + supply (Buff* mem, size_t siz) { inQueue_.push ({mem,siz}); } @@ -248,7 +242,7 @@ namespace mem { * @warning **not threadsafe** — invoke from worker thread only */ void - reAdd (Buff* mem) + reSupply (Buff* mem) { ingest(); Block* block = UN_CONST (find (mem)); diff --git a/tests/vault/mem/engine-buffer-manager-test.cpp b/tests/vault/mem/engine-buffer-manager-test.cpp index 4e0a17268..b77c9c21c 100644 --- a/tests/vault/mem/engine-buffer-manager-test.cpp +++ b/tests/vault/mem/engine-buffer-manager-test.cpp @@ -20,6 +20,7 @@ #include "test/test-helper.hpp" //#include "vault/mem/engine-buffer-metadata.hpp" #include "vault/mem/engine-buffer-manager.hpp" +#include "vault/mem/engine-buffer-allocator.hpp" //#include "lib/depend-inject.hpp" //#include "lib/thread.hpp" //#include "lib/error.hpp" @@ -83,7 +84,7 @@ namespace test { void demonstrate_AllocatorInterface() { - using Allo = EngineBufferManager::BufferAllocator; + using Allo = HeapBufferAllocator; CHECK (lib::allo::is_Stateless_v); Allo allo; diff --git a/tests/vault/mem/local-mem-pool-test.cpp b/tests/vault/mem/local-mem-pool-test.cpp index 25838ebe7..66faeebd6 100644 --- a/tests/vault/mem/local-mem-pool-test.cpp +++ b/tests/vault/mem/local-mem-pool-test.cpp @@ -85,7 +85,7 @@ namespace test { simpleUse() { LocalMemPool pool; - pool.add (MEM1, SIZ20); + pool.supply (MEM1, SIZ20); CHECK (pool.canServe(SIZ20)); CHECK (pool.canServe(SIZ10)); @@ -94,7 +94,7 @@ namespace test { CHECK (SIZ20 == siz); CHECK (not pool.canServe(SIZ10)); - pool.reAdd (MEM1); + pool.reSupply (MEM1); CHECK (pool.canServe(SIZ20)); } @@ -113,7 +113,7 @@ namespace test { CHECK (watch(pool).isEmpty()); // Feed new allocations through the in-queue... - pool.add (MEM1, SIZ10); + pool.supply (MEM1, SIZ10); CHECK (watch(pool).cnt(SIZ10) == 0); // new allotment not ingested yet,.... CHECK (not watch(pool).isEmpty()); // however, there is an entry in the in-queue @@ -121,8 +121,8 @@ namespace test { CHECK (watch(pool).cnt(SIZ10) == 1); CHECK (not watch(pool).isEmpty()); - pool.add (MEM2, SIZ20); - pool.add (MEM3, SIZ30); + pool.supply (MEM2, SIZ20); + pool.supply (MEM3, SIZ30); CHECK (pool.canServe(SIZ20)); CHECK (pool.canServe(SIZ10)); CHECK (watch(pool).size() == 3); @@ -146,8 +146,8 @@ namespace test { CHECK (pool.canServe(SIZ30)); // Return allocations back into the pool - pool.reAdd (m2); - pool.reAdd (m1); + pool.reSupply (m2); + pool.reSupply (m1); CHECK (watch(pool).cntFree() == 3); // Shrink / clean-up will return allocations through a consumer @@ -172,7 +172,7 @@ namespace test { // add a further allocation with SIZ30 CHECK (not pool.canServe(SIZ30)); - pool.add (MEM3, SIZ30); + pool.supply (MEM3, SIZ30); CHECK (watch(pool).size() == 1); CHECK ( pool.canServe(SIZ30)); CHECK (watch(pool).size() == 2); @@ -202,10 +202,10 @@ namespace test { verify_selectAlloc() { LocalMemPool pool; - pool.add (MEM11, SIZ10); - pool.add (MEM31, SIZ30); - pool.add (MEM32, SIZ30); - pool.add (MEM33, SIZ30); + pool.supply (MEM11, SIZ10); + pool.supply (MEM31, SIZ30); + pool.supply (MEM32, SIZ30); + pool.supply (MEM33, SIZ30); uint cnt = pool.reserve(4, SIZ10); // request to reserve 4 blocks of SIZ10 CHECK (cnt == 1); // yet only one block is a good match and thus reserved @@ -226,12 +226,12 @@ namespace test { CHECK (watch(pool).size() == 4); // return all blocks... - pool.reAdd (m1); - pool.reAdd (m2); - pool.reAdd (m3); - VERIFY_FAIL ("unknown allocation", pool.reAdd (m4)) - VERIFY_FAIL ("unknown allocation", pool.reAdd (fake_Buffer(12345))) - VERIFY_FAIL ("not marked as used", pool.reAdd (MEM31)) + pool.reSupply (m1); + pool.reSupply (m2); + pool.reSupply (m3); + VERIFY_FAIL ("unknown allocation", pool.reSupply (m4)) + VERIFY_FAIL ("unknown allocation", pool.reSupply (fake_Buffer(12345))) + VERIFY_FAIL ("not marked as used", pool.reSupply (MEM31)) CHECK (watch(pool).cntFree() == 4); // all entries in the pool were scored.... @@ -241,7 +241,7 @@ namespace test { CHECK (watch(pool).getScore(MEM11) == 10); // this one was used as a perfect match and thus got the full score // this score determines how they are used further.... - pool.add (MEM12, SIZ10); + pool.supply (MEM12, SIZ10); auto [m5,s5] = pool.retrieve (SIZ10-5); auto [m6,s6] = pool.retrieve (SIZ10); auto [m7,s7] = pool.retrieve (SIZ10); @@ -255,10 +255,10 @@ namespace test { CHECK (watch(pool).cntFree() == 0); CHECK (watch(pool).size() == 5); - pool.reAdd (m8); - pool.reAdd (m7); - pool.reAdd (m6); - pool.reAdd (m5); + pool.reSupply (m8); + pool.reSupply (m7); + pool.reSupply (m6); + pool.reSupply (m5); CHECK (watch(pool).cntFree() == 4); CHECK (watch(pool).getScore(MEM11) == 15); // got +5 score because the match quality was only 50% (SIZ10 but only 5 needed) CHECK (watch(pool).getScore(MEM12) == 10); // this one was new, but got a +10 score due to the perfect match @@ -282,11 +282,11 @@ namespace test { CHECK (watch(pool).getScore(MEM11) == 5); // Scores were reduced accordingly, cutting away a the »kill level« of +10 CHECK (watch(pool).getScore(MEM31) == -6); - pool.reAdd (MEM31); + pool.reSupply (MEM31); auto [mX,sX] = pool.retrieve (SIZ20); CHECK (mX == MEM31); CHECK (sX == SIZ30); - pool.reAdd (MEM31); + pool.reSupply (MEM31); CHECK (watch(pool).cntFree() == 2); CHECK (watch(pool).getScore(MEM11) == 3); // was penalised with -2 since MEM11 was too small to satisfy a SIZ20 request CHECK (watch(pool).getScore(MEM31) == 0); // while MEM31 got a score of +6 for a usage with 60% match quality (SIZ30 used for SIZ20) diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 591aea399..51eb5d3e1 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -77983,6 +77983,9 @@ + + + @@ -91575,6 +91578,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ @@ -143914,6 +143918,63 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) + + + + + + + +

+ Ein Job will seine Eingabedaten in einem Buffer ⟶ Zugang per BuffHandle +

+ +
+ +
+ + + + + + + +

+ Es muß irgend eine Art temporäre Index-Tabelle geben, damit wir einen einfachen Code für das LocalTag bekommen. (Denkbar wäre auch ein Hash)... +

+

+ +

+

+ Letztlich sind alle im Projekt verwendeten Medien als Assets verzeichnet, und von dort müßte es eine Auflösung geben, die dann in einen IO-Job übersetzt werden kann +

+ +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + @@ -145418,7 +145479,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + + @@ -146103,24 +146165,19 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - - +

...das wäre nur der Fall, wenn ein Unit-Test „mal eben“ eine Render-Engine braucht. Denn sonst decken wir den Default-Fall bereits mit dem NaiveBufferSetup ab

- -
+
- + - - - +

Und dann würde jeder Zugriff mit einer Exception scheitern.... @@ -146132,8 +146189,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) DependInject<Base>::useSingleton<Sub>

- -
+
@@ -146152,32 +146208,26 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - - +

Schon allein um die konfigurierbaren Aspekte klar herauszustellen, und um ggfs. Quellen dynamischer Konfiguration zu verdrahten

- -
+
- - - +

...das ist die übliche Cache-Line-Size auf x86_64

- -
+
@@ -146185,31 +146235,25 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - - +

auch wenn es erst mal gedanklich schwerfällt, und für die zunächst angestrebte naive Implementierung wie ein sinnloser Tanz aussieht; denn dieses Framework ist gut durchdacht und sollte langsam zu einem Rückgrat für Allokationsvorgänge werden. Zur Erinnerung: dieses Konzept habe ich an die Stelle eines »globalen Allokators« gestellt. Zunächst hatten wir ja den Ansatz mit Christian's "Mempool" — und ich habe mich jahrelang mit der Planlosigkeit dieser Idee herumgeplagt, bis ich langsam verstanden habe, was daran schief ist....

- -
+ - - - +

Allokatoren sind composable

- -
+ @@ -146218,44 +146262,33 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - - +

weil sie für einen naiven Allokator nur schlecht zu implementieren ist

- -
+ - - - +

...da wir ja letztlich alles an den globalen Allokator durchreichen wollen (weil der's sowiso besser kann), haben wir keinerlei Struktur mehr, die wir uns zunutze machen können ⟹ für jede de-Allokation müssen wir den Tracking-Eintrag suchen

- -
+
- - - +

für eine Pool-Hierarchie dagegen ist eine effiziente Implementierung möglich

- -
+ - - - +

hierbei kann man sich die bekannte Element-Größe zunutze machen @@ -146272,8 +146305,296 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - + + + + + + + + + + +

+ Ich betrachte das alles hier als »tief in der Implementierung«, und zudem performance-kritisch. Daher halte ich eine einfache Struktur mit einer festen Konvention für angemessen. Das ganze Setup funktioniert ohnehin nur in einer speziell abgezierkelten Konfiguration, und muß darüber hinaus auch von niemandem verstanden und verwendet werden. Die sichtbare Schnittstelle ist der BufferProvider +

+ +
+ + + + + + + + + + + + + + + + +

+ und jede vereinheitlichende Konvention wird verwirrend +

+ +
+ + + +

+ weil die Struktur von sich selber her nicht uniform ist: +

+
    +
  • + eingehende Allocation-Requests müssen an diverse lokale Pools zugestellt werden +
  • +
  • + zurückkommende Allokationen dagegen haben zwar eine Speicher-Addresse des Buffers, dafür aber gehen sie stets in eine einzige Funktion im EngineBufferManager +
  • +
+ +
+
+
+
+ + + + + + + + + + +

+ zu groß und zu schwer!!! +

+ +
+ +
+ + + + +

+ Zur Erinnerung (hatte mir vor ein paar Wochen das Thema bereits im Detail analysiert, als ich die Implementierungs-Skizze für die »LocalFun« gemacht habe) +

+ +
+
+ + + + + + + +

+ und das ist der thread-local pool-Allocator +

+ +
+
+
+
+ + + + + + +

+ läuft auf eine mutual dependency hinaus.... +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +

+ Man würde höchstens Teile der Implementierung auseinanderreißen, würde aber die Implementierungs-Logik größtenteils auch in den Deklarationen aufdoppeln. Den gleichen Effekt bekommt man bereits durch eine Trennung Header / Translation-Unit; und es gibt keine Stelle im Code, die nur rein auf Ebene der Abstraktion für sich verständlich wäre +

+ +
+
+
+ + + + + + + + + + + + +

+ Zunächst einmal notwendig, damit ich den auch in einem Test verwenden kann; längerfristig ist das auch sinnvoll, denn der Buffer-Allocator selber wird komplex werden (ich plane ein System von Pools) +

+ +
+
+
+ + +
+ + + + + + +

+ ...denn der Callback muß ja an den lokalen Pool im Thread gehen, und den wiederum wollte ich von der weiteren Implementierung isoliert halten (da er allein für sich schon komplex genug ist, und einen eigenen Test rechtfertigt). Der lokale Pool muß zwingend direkt per Referenz angesprochen werden, da die Zustellung der beauftragten Allokationen aus einem anderen Thread heraus erfolgt (und die Allokationen gehen dann im LocalMemPool auch noch in eine Eingangsqueue) +

+ +
+
+ + + + + + + + + + +

+ praktisch jede der diskutierten Varianten wäre irgendwie machbar — es könnten sich sogar beite Implementierungen wechselseitig aufrufen (sofern wenigstens eine in einer separaten Translation-Unit residiert). Jedoch wird dieses Aggregat absehbar ein komplexes (und auch fragiles) Stück der Implementierung, und eine solche wechselseitige Abhängigkeit ist viel leichter gebaut, als verstanden. +

+ +
+
+ + + + + +

+ ...insofern sich damit vermeiden ließe, ein pseudu-bedeutsames Interface zu erfinden +

+ +
+
+ + + + +

+ ...nämlich für den localBufferStore; allerdings hat diese Translation-Unit keinerlei eigene Substanz, da alle Typen bereits im Header voll definiert sein müssen, wegen der Lock-free-Queue und derm LocalSlice +

+ +
+
+ + + + + + + + + +

+ Das ist klar im Fall vom LocalMemPool: es gibt nur einen einzigen Verknüpfungspunkt, nämlich die consume_all(λ) +

+ +
+
+ + + + +

+ damit wäre es auch keine abstrakte Methode mehr — sondern ein Baustein +

+ +
+
+
+
+
+ + + + + + + + + + + + + + +

+ später versucht ein anderer Thread, +

+

+ die angeforderten Allokationen zuzustellen +

+

+ ↯ +

+ +
+ +
+
+
+
+
+
+ + + + + + + +

+ es ist auch sinnvoll, sie zuerst en bloc zu verarbeiten +

+ +
+ + + +

+ weil dadurch nämlich zusätzlicher Speicer zur Verfügung steht, und ggfs. sogar die Fragmentierung reduziert wird +

+ +
@@ -146293,16 +146614,13 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - - +

Es ist nicht sinnvoll, eine »vorläufige« Implementierung eines heterogenen Memory-Managers zu machen. Da ist nämlich der Heap-Allokator  stets besser geeignet — ist inzwischen einigermaßen schwer geworden, den Heap-Allokator überhaupt noch zu schlagen. Mithin wird das auch gleich eine gute Baseline-Implementierung für Performance-Messungen

- -
+
@@ -146314,16 +146632,13 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - - +

Das ist nun wirklich mehr eine Demonstration, denn ich kann da praktisch nichts verifizieren (außer daß es keinen SEGFAULT gibt, wenn ich in den allozierten Speicher schreibe). Wenigstens ist damit schon mal sichergestellt, daß der Code durch den Compiler geht

- -
+
@@ -146331,16 +146646,13 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - - +

Das heißt, wir müssen uns darauf verlassen, daß der ganze komplexe Klapperatismus absolut korrekt funktioniert; wenn dem so ist, genügt es, einfach blindlings neue Allokationen an die lokalen Pools zu schicken und freizugeben was aus den lokalen Pools zurückgesendet wird. Das hat zur Konsequenz daß gewisse zusätzliche Konsistenzchecks eingebaut werden müssen, damit sich die Korrektheit auch erweisen läßt

- -
+
@@ -146390,6 +146702,65 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
+ + + + + + + + + + + + + + + + +

+ ...wie wird der IO-Vorgang im Modell dargestellt? +

+
    +
  • + man könnte ihn komplett heraushalten und in einer separaten Datenstruktur verwalten, die dann auch als Index-Tabelle dienen könnte zur Auflösung einer Quellen-ID +
  • +
  • + man könnte eine speziell präparierte Quell-Node einführen, die sich dann aber im shed() und im weft()-Schritt anders verhalten müßte +
  • +
+ +
+
+
+ + + + +

+ Zum Vergleich: eine gewöhnliche Node mit Media-Weaving-Pattern „findet“ nach dem pull()-Schritt einen Satz allozierter Vorläufer-Buffer vor. Aber hier gibt es keine Vorläufer, also fällt das pull() leer durch. Mithin ist zu klären, wann und wie diese Quellbufer belegt werden +

+ +
+ +
+ + + + +

+ Das dürfte sogar der Regelfall sein; der IO-Job hat ein gewisses Datenfenster in einen Buffer geladen, das dort aber noch komprimiert vorliegt. Als erstes muß nun ein Codec-Schritt aufgerufen werden, der als Resultat eine »Group-of-Pictures« in einen Satz von Buffern ausgibt. Aber der nächste Schritt in der Pipeline braucht nur genau einen von diesen Buffern, während alle weiteren Render-Jobs, die sich aus der gleichen GoP bedienen, den Decoder-Schritt überhaupt nicht mehr machen müssen. Wie stellt man das am geschicktesten im Modell dar? Naheliegend wäre, die Verbindung über den Cache herzustellen, d.h. pro forma würde jeder pull() wieder auch den Decoder-Schritt aktivieren, aber da die decodierten Frames bereits im Cache liegen, würde dieser Schritt stillschweigend übersprungen +

+ +
+ + + + +
+
+
+
@@ -171263,7 +171634,12 @@ std::cout << tmpl.render({"what", "World"}) << s - + + + + + +