diff --git a/src/vault/mem/local-buffer-store.hpp b/src/vault/mem/local-buffer-store.hpp index afbff2ff1..a35dd91a8 100644 --- a/src/vault/mem/local-buffer-store.hpp +++ b/src/vault/mem/local-buffer-store.hpp @@ -51,6 +51,12 @@ namespace vault { namespace mem { + namespace { // tuning parameters for clean-up heuristics + + const double TRIGGER_SENSITIVIY = 1.0; ///< sensitivity of re-triggering based on max-score and turnover + const double HEURISTIC_CLEANUP = 0.5; ///< cut-off percentage of max-score; blocks with lower score will be sent back + } + using util::_Fmt; class LocalStoreDiagnostic; @@ -73,16 +79,28 @@ namespace mem { * This implementation of the BufferProvider::BufferStore interface * deliberately skips further sanity checks, beyond what is enforced * through the BufferMetadata lifecycle. Allocation requests are - * passed to the LocalMemPool for the current thread. + * delegated to the LocalMemPool for the current thread. + * + * The PoolSetup creates a connection to the EngineBufferManager, + * retrieved via lib::Depend, so each thread-local pool can unwind + * automatically at thread end and return all leased storage. */ class LocalBufferStore : public BufferProviderSetup::Store { - struct SetupPool; - using LocalPool = lib::LocalSlice; + struct PoolSetup; + using LocalPool = lib::LocalSlice; using GlobalPool = lib::Depend; - struct SetupPool + static void + send_back2globalPool (Buff* mem, size_t siz) + { + Alloc alloc{mem,siz}; + GlobalPool globalPool; + globalPool().supply(alloc); + } + + struct PoolSetup { using Service = LocalMemPool; @@ -91,12 +109,7 @@ namespace mem { { return LocalMemPool{[](LocalMemPool& pool2close) { - pool2close.purge([](Buff* mem, size_t siz) - { - Alloc alloc{mem,siz}; - GlobalPool globalPool; - globalPool().supply(alloc); - }); + pool2close.purge (send_back2globalPool); }}; } }; @@ -105,8 +118,7 @@ namespace mem { GlobalPool globalPool_; public: - LocalBufferStore() - { } + LocalBufferStore() { } @@ -191,15 +203,19 @@ namespace mem { auto& [storage,buffSiz,specifics] = storageSlot; REQUIRE (storage); localPool_->reSupply (storage); - /////////////////////////////////////////////////////OOO trigger heuristic clean-up here + if (localPool_->shouldCleanup (TRIGGER_SENSITIVIY)) + localPool_->cleanup (HEURISTIC_CLEANUP + ,send_back2globalPool); } + private: /** diagnostic access for unit testing */ friend PoolDiagnostic watch (LocalBufferStore const&); }; + /** entrance point to inspection for test */ inline PoolDiagnostic watch (LocalBufferStore const& lstor) diff --git a/src/vault/mem/local-mem-pool.hpp b/src/vault/mem/local-mem-pool.hpp index 849d689b2..546cce0f6 100644 --- a/src/vault/mem/local-mem-pool.hpp +++ b/src/vault/mem/local-mem-pool.hpp @@ -205,9 +205,9 @@ namespace mem { /** turnover-based trigger for heuristic pool clean-up */ bool - shouldCleanup (float sharpen =1.0f) const + shouldCleanup (double sharpen =1.0f) const { - return turnover_ * float(MATCH_SCORE) <= maxScore_ * sharpen; + return turnover_ * double(MATCH_SCORE) <= maxScore_ * sharpen; } /** diff --git a/tests/vault/mem/local-buffer-store-test.cpp b/tests/vault/mem/local-buffer-store-test.cpp index fbfaa60ee..45888731f 100644 --- a/tests/vault/mem/local-buffer-store-test.cpp +++ b/tests/vault/mem/local-buffer-store-test.cpp @@ -24,7 +24,6 @@ #include "lib/depend-inject.hpp" #include "lib/thread.hpp" #include "lib/util.hpp" -#include "test/diagnostic-output.hpp"///////////////////////TODO #include #include @@ -40,28 +39,11 @@ namespace vault { namespace mem { namespace test { -// using LERR_(LOGIC); -// using LERR_(LIFECYCLE); - namespace { // Test helper - -// template -// Buff* -// mark_as_Buffer(X& something) -// { -// return reinterpret_cast (std::addressof(something)); -// } -// - } - - - - - - - /**************************************************************************//** - * @test verify worker memory handling that relies on a thread-local allocator. + /*********************************************//** + * @test verify memory handling in worker + * that relies on a thread-local allocation pool. */ class LocalBufferStore_test : public Test { @@ -69,8 +51,6 @@ namespace test { virtual void run (Arg) { -// seedRand(); - verify_API(); verify_announce(); verify_allocate(); @@ -372,7 +352,7 @@ namespace test { /** @test heuristic clean-up after releasing several allocations * - at first, a de-allocated block remains available in the local pool * - after a number of usages, defined as _turnover_, clean-up is triggered - * - this test scenario requests 3 buffers, but repeatedly uses only one + * - this test scenario requests 5 buffers, but repeatedly uses only one * - after the third round with this usage pattern, the local pool is reduced */ void @@ -394,7 +374,7 @@ namespace test { Thread testWorker{[&] /* === Scenario: test subject requests allocation immediately === */ { // Step-1 : first round - uint avail = storeAPI.prepareBuffers(TYPEID, 3, BUFFSIZ); + uint avail = storeAPI.prepareBuffers(TYPEID, 5, BUFFSIZ); CHECK (0 == avail); CHECK (watch(localStore).isEmpty()); @@ -403,36 +383,40 @@ namespace test { CHECK (storage); storeAPI.detachBuffer (TYPEID, slot1); - // Result: got three allocations and all three are in the local pool - CHECK (watch(localStore).size() == 3); - CHECK (watch(localStore).cntFree() == 3); + // Result: got five allocations and all are in the local pool + CHECK (watch(localStore).size() == 5); + CHECK (watch(localStore).cntFree() == 5); ++step; - // Step-2 : repeat same pattern + // Step-2 : repeat same pattern -- again only using one of the five buffers BuffAlloc slot2 = storeAPI.provideBuffer (TYPEID, BUFFSIZ, 0,0); CHECK (slot2 == slot1); - CHECK (watch(localStore).size() == 3); - CHECK (watch(localStore).cntFree() == 2); + CHECK (watch(localStore).size() == 5); + CHECK (watch(localStore).cntFree() == 4); storeAPI.detachBuffer (TYPEID, slot1); - CHECK (watch(localStore).size() == 3); - CHECK (watch(localStore).cntFree() == 3); + CHECK (watch(localStore).size() == 5); + CHECK (watch(localStore).cntFree() == 5); ++step; // Step-3 : repeat for a third time BuffAlloc slot3 = storeAPI.provideBuffer (TYPEID, BUFFSIZ, 0,0); CHECK (slot3 == slot1); - CHECK (watch(localStore).size() == 3); - CHECK (watch(localStore).cntFree() == 2); + CHECK (watch(localStore).size() == 5); + CHECK (watch(localStore).cntFree() == 4); storeAPI.detachBuffer (TYPEID, slot1); - // this completed a "turnover" of three buffers - SHOW_EXPR(watch(localStore).size()); + // this triggered a clean-up and detected the unused capacity + CHECK (watch(localStore).size() == 1); + CHECK (watch(localStore).cntFree() == 1); ++step; // main thread will now see the partial clean-up - }}; // block automatically sent back after thread end + while (step == 4) // wait until main thread could investigate + sleep_for(1ms); + + }}; // remaining buffer automatically sent back after thread end /* === Main thread: observe effects in the global pool === */ @@ -440,19 +424,31 @@ namespace test { // wait until Step-2 while (step < 2) sleep_for(1ms); - CHECK (watch(*globalPool).numAllocs() == 3); + CHECK (watch(*globalPool).numAllocs() == 5); // re-check after Step-2 while (step <= 2) sleep_for(1ms); // nothing changed; local pool operates independently - CHECK (watch(*globalPool).numAllocs() == 3); + CHECK (watch(*globalPool).numAllocs() == 5); + CHECK (watch(*globalPool).bytesLeased() == 5*BUFFSIZ); // re-check after Step-3 while (step <= 3) sleep_for(1ms); globalPool->processPendingRequests(); - SHOW_EXPR(watch(*globalPool).numAllocs()) + CHECK (watch(*globalPool).bytesLeased() == 1*BUFFSIZ); + // Note: local pool sent back excess capacity + + ++step; // allow the worker to terminate after that + + while (testWorker) + sleep_for(1ms); + sleep_for(1ms); // additional wait for the dtor + + globalPool->processPendingRequests(); + CHECK (watch(*globalPool).bytesLeased() == 0); + // all capacity properly returned into global pool } }; diff --git a/tests/vault/mem/local-mem-pool-test.cpp b/tests/vault/mem/local-mem-pool-test.cpp index b17798a47..f5f1f12a4 100644 --- a/tests/vault/mem/local-mem-pool-test.cpp +++ b/tests/vault/mem/local-mem-pool-test.cpp @@ -306,6 +306,7 @@ namespace test { + /** @test heuristic trigger mechanism for pool clean-up * - use a setup where the global pool is just a queue of buffer pointers * - create abbreviated notation to play through various scenarios @@ -377,208 +378,199 @@ namespace test { .feed(MEM32) .feed(MEM33); -SHOW_EXPR(show(globalPool)); -SHOW_EXPR(watch(pool).size()) -SHOW_EXPR(watch(pool).getMaxScore()) -SHOW_EXPR(watch(pool).getTurnover()) + + CHECK (0 == watch(pool).size() ); + CHECK (0 == watch(pool).getTurnover() ); supply(3); - -SHOW_EXPR("supply") -SHOW_EXPR(show(globalPool)); -SHOW_EXPR(watch(pool).size()) -SHOW_EXPR(watch(pool).cntFree()) - reserve(2); -SHOW_EXPR("reserve") -SHOW_EXPR(watch(pool).size()) -SHOW_EXPR(watch(pool).cntFree()) -SHOW_EXPR(watch(pool).cntResd()) -SHOW_EXPR(watch(pool).getMaxScore()) -SHOW_EXPR(watch(pool).getTurnover()) + CHECK (3 == watch(pool).size() ); + CHECK (3 == watch(pool).cntFree() ); + CHECK (2 == watch(pool).cntResd() ); + CHECK (1 == watch(pool).getMaxScore() ); + CHECK (3 == watch(pool).getTurnover() ); + + //------------ PATTERN-1 : use complete local capacity claim(3); -SHOW_EXPR("claim") -SHOW_EXPR(watch(pool).size()) -SHOW_EXPR(watch(pool).cntFree()) -SHOW_EXPR(watch(pool).cntResd()) -SHOW_EXPR(watch(pool).getMaxScore()) -SHOW_EXPR(watch(pool).getTurnover()) -SHOW_EXPR(show(usedBuff)); + CHECK ( 3 == watch(pool).size() ); + CHECK ( 0 == watch(pool).cntFree() ); + CHECK ( 0 == watch(pool).cntResd() ); + CHECK (10 == watch(pool).getMaxScore() ); + CHECK ( 3 == watch(pool).getTurnover() ); free(3); -SHOW_EXPR("free") -SHOW_EXPR(watch(pool).size()) -SHOW_EXPR(watch(pool).cntFree()) -SHOW_EXPR(watch(pool).cntResd()) -SHOW_EXPR(watch(pool).getMaxScore()) -SHOW_EXPR(watch(pool).getTurnover()) -SHOW_EXPR(show(usedBuff)); + CHECK ( 3 == watch(pool).size() ); + CHECK ( 3 == watch(pool).cntFree() ); + CHECK (10 == watch(pool).getMaxScore() ); + CHECK ( 0 == watch(pool).getTurnover() ); -SHOW_EXPR(pool.shouldCleanup()) + // heuristic trigger fires at that point, + // since full capacity was »turned over« once + CHECK (pool.shouldCleanup() ); uint removed = pool.cleanup(0.5, pushMem); -SHOW_EXPR(removed) + // However, nothing was removed, since all elements + // were used efficiently and are thus close to maxScore + CHECK (0 == removed); + CHECK (show(globalPool) == "21, 31, 32, 33"_expect); -SHOW_EXPR(watch(pool).size()) -SHOW_EXPR(watch(pool).getMaxScore()) -SHOW_EXPR(watch(pool).getTurnover()) -SHOW_EXPR(show(globalPool)); + CHECK ( 3 == watch(pool).size() ); + CHECK ( 5 == watch(pool).getMaxScore() ); + // turnover is re-set to current pool size + CHECK ( 3 == watch(pool).getTurnover() ); + + //------------ PATTERN-2 : use only one buffer a time (inefficient) claim(1); -SHOW_EXPR("claim") -SHOW_EXPR(watch(pool).size()) -SHOW_EXPR(watch(pool).cntFree()) -SHOW_EXPR(watch(pool).cntResd()) -SHOW_EXPR(watch(pool).getMaxScore()) -SHOW_EXPR(watch(pool).getTurnover()) + CHECK ( 3 == watch(pool).size() ); + CHECK ( 2 == watch(pool).cntFree() ); + CHECK (15 == watch(pool).getMaxScore() ); + CHECK ( 3 == watch(pool).getTurnover() ); free(1); -SHOW_EXPR("free") -SHOW_EXPR(watch(pool).size()) -SHOW_EXPR(watch(pool).cntFree()) -SHOW_EXPR(watch(pool).cntResd()) -SHOW_EXPR(watch(pool).getMaxScore()) -SHOW_EXPR(watch(pool).getTurnover()) + CHECK ( 3 == watch(pool).size() ); + CHECK ( 3 == watch(pool).cntFree() ); + CHECK (15 == watch(pool).getMaxScore() ); + CHECK ( 2 == watch(pool).getTurnover() ); -SHOW_EXPR(pool.shouldCleanup()) + // not yet hitting the trigger... + CHECK (not pool.shouldCleanup() ); claim(1); -SHOW_EXPR("claim") -SHOW_EXPR(watch(pool).size()) -SHOW_EXPR(watch(pool).cntFree()) -SHOW_EXPR(watch(pool).cntResd()) -SHOW_EXPR(watch(pool).getMaxScore()) -SHOW_EXPR(watch(pool).getTurnover()) + CHECK ( 3 == watch(pool).size() ); + CHECK ( 2 == watch(pool).cntFree() ); + CHECK (25 == watch(pool).getMaxScore() ); + CHECK ( 2 == watch(pool).getTurnover() ); free(1); -SHOW_EXPR("free") -SHOW_EXPR(watch(pool).size()) -SHOW_EXPR(watch(pool).cntFree()) -SHOW_EXPR(watch(pool).getMaxScore()) -SHOW_EXPR(watch(pool).getTurnover()) + CHECK ( 3 == watch(pool).size() ); + CHECK ( 3 == watch(pool).cntFree() ); + CHECK (25 == watch(pool).getMaxScore() ); + CHECK ( 1 == watch(pool).getTurnover() ); -SHOW_EXPR(pool.shouldCleanup()) + // maxScore did increase a lot, + // since effectively just a single buffer was used all the time + + CHECK (pool.shouldCleanup() ); removed = pool.cleanup(0.5, pushMem); -SHOW_EXPR(removed) + // Consequently the two superfluous buffers will be sent back + // (cutoff-score is at 12; maxScore will be lowered by that amount afterwards) + CHECK (2 == removed ); + CHECK (show(globalPool) == "21, 31, 32, 33, 12, 11"_expect); + + CHECK ( 1 == watch(pool).size() ); + CHECK ( 1 == watch(pool).cntFree() ); + CHECK (13 == watch(pool).getMaxScore() ); + CHECK ( 1 == watch(pool).getTurnover() ); -SHOW_EXPR(watch(pool).size()) -SHOW_EXPR(watch(pool).getMaxScore()) -SHOW_EXPR(watch(pool).getTurnover()) -SHOW_EXPR(show(globalPool)); + //------------ PATTERN-3 : dynamic partially overlapping usage supply(4); -SHOW_EXPR("supply") -SHOW_EXPR(watch(pool).size()) -SHOW_EXPR(watch(pool).getMaxScore()) -SHOW_EXPR(watch(pool).getTurnover()) -SHOW_EXPR(show(globalPool)); + + CHECK ( 5 == watch(pool).size() ); + CHECK ( 5 == watch(pool).cntFree() ); + CHECK (13 == watch(pool).getMaxScore() ); + CHECK ( 5 == watch(pool).getTurnover() ); + // turnover was bumped +4 due to increased supply claim(3); -SHOW_EXPR("claim") -SHOW_EXPR(watch(pool).size()) -SHOW_EXPR(watch(pool).cntFree()) -SHOW_EXPR(watch(pool).cntResd()) -SHOW_EXPR(watch(pool).getMaxScore()) -SHOW_EXPR(watch(pool).getTurnover()) + CHECK ( 5 == watch(pool).size() ); + CHECK ( 2 == watch(pool).cntFree() ); + CHECK (13 == watch(pool).getMaxScore() ); + CHECK ( 5 == watch(pool).getTurnover() ); free(2); -SHOW_EXPR("free") -SHOW_EXPR(watch(pool).size()) -SHOW_EXPR(watch(pool).cntFree()) -SHOW_EXPR(watch(pool).cntResd()) -SHOW_EXPR(watch(pool).getMaxScore()) -SHOW_EXPR(watch(pool).getTurnover()) + CHECK ( 5 == watch(pool).size() ); + CHECK ( 4 == watch(pool).cntFree() ); + CHECK (13 == watch(pool).getMaxScore() ); + CHECK ( 3 == watch(pool).getTurnover() ); -SHOW_EXPR(pool.shouldCleanup()) + CHECK (not pool.shouldCleanup() ); + // no complete turnover cycle yet claim(2); -SHOW_EXPR("claim") -SHOW_EXPR(watch(pool).size()) -SHOW_EXPR(watch(pool).cntFree()) -SHOW_EXPR(watch(pool).cntResd()) -SHOW_EXPR(watch(pool).getMaxScore()) -SHOW_EXPR(watch(pool).getTurnover()) + CHECK ( 5 == watch(pool).size() ); + CHECK ( 2 == watch(pool).cntFree() ); + CHECK (20 == watch(pool).getMaxScore() ); + CHECK ( 3 == watch(pool).getTurnover() ); supply(4); -SHOW_EXPR("supply") -SHOW_EXPR(watch(pool).size()) -SHOW_EXPR(watch(pool).getMaxScore()) -SHOW_EXPR(watch(pool).getTurnover()) -SHOW_EXPR(show(globalPool)); + + CHECK ( 7 == watch(pool).size() ); + CHECK ( 4 == watch(pool).cntFree() ); + CHECK (20 == watch(pool).getMaxScore() ); + CHECK ( 5 == watch(pool).getTurnover() ); free(2); -SHOW_EXPR("free") -SHOW_EXPR(watch(pool).size()) -SHOW_EXPR(watch(pool).cntFree()) -SHOW_EXPR(watch(pool).getMaxScore()) -SHOW_EXPR(watch(pool).getTurnover()) -SHOW_EXPR(show(usedBuff)); + CHECK ( 7 == watch(pool).size() ); + CHECK ( 6 == watch(pool).cntFree() ); + CHECK (20 == watch(pool).getMaxScore() ); + CHECK ( 3 == watch(pool).getTurnover() ); claim(1); -SHOW_EXPR("claim") -SHOW_EXPR(watch(pool).size()) -SHOW_EXPR(watch(pool).cntFree()) -SHOW_EXPR(watch(pool).cntResd()) -SHOW_EXPR(watch(pool).getMaxScore()) -SHOW_EXPR(watch(pool).getTurnover()) + CHECK ( 7 == watch(pool).size() ); + CHECK ( 5 == watch(pool).cntFree() ); + CHECK (20 == watch(pool).getMaxScore() ); + CHECK ( 3 == watch(pool).getTurnover() ); free(1); -SHOW_EXPR("free") -SHOW_EXPR(watch(pool).size()) -SHOW_EXPR(watch(pool).cntFree()) -SHOW_EXPR(watch(pool).getMaxScore()) -SHOW_EXPR(watch(pool).getTurnover()) -SHOW_EXPR(show(usedBuff)); + CHECK ( 7 == watch(pool).size() ); + CHECK ( 6 == watch(pool).cntFree() ); + CHECK (20 == watch(pool).getMaxScore() ); + CHECK ( 2 == watch(pool).getTurnover() ); + + CHECK (pool.shouldCleanup()); + // triggering now, since 20/MATCH_SCORE ≡ 2 -SHOW_EXPR(pool.shouldCleanup()) removed = pool.cleanup(0.5, pushMem); -SHOW_EXPR(removed) + CHECK (3 == removed); + CHECK (show(globalPool) == "11, 12, 21"_expect); -SHOW_EXPR(watch(pool).size()) -SHOW_EXPR(watch(pool).getMaxScore()) -SHOW_EXPR(watch(pool).getTurnover()) -SHOW_EXPR(show(globalPool)); + CHECK ( 4 == watch(pool).size() ); + CHECK ( 3 == watch(pool).cntFree() ); + CHECK (10 == watch(pool).getMaxScore() ); + CHECK ( 4 == watch(pool).getTurnover() ); + + //------------ PATTERN-4 : decreasing the fraction used claim(1); -SHOW_EXPR("claim") -SHOW_EXPR(watch(pool).size()) -SHOW_EXPR(watch(pool).cntFree()) -SHOW_EXPR(watch(pool).cntResd()) -SHOW_EXPR(watch(pool).getMaxScore()) -SHOW_EXPR(watch(pool).getTurnover()) + CHECK ( 4 == watch(pool).size() ); + CHECK ( 2 == watch(pool).cntFree() ); + CHECK (20 == watch(pool).getMaxScore() ); + CHECK ( 4 == watch(pool).getTurnover() ); free(2); -SHOW_EXPR("free") -SHOW_EXPR(watch(pool).size()) -SHOW_EXPR(watch(pool).cntFree()) -SHOW_EXPR(watch(pool).getMaxScore()) -SHOW_EXPR(watch(pool).getTurnover()) + CHECK ( 4 == watch(pool).size() ); + CHECK ( 4 == watch(pool).cntFree() ); + CHECK (20 == watch(pool).getMaxScore() ); + CHECK ( 2 == watch(pool).getTurnover() ); -SHOW_EXPR(pool.shouldCleanup()) - removed = pool.cleanup(0.5, pushMem); -SHOW_EXPR(removed) + CHECK (pool.shouldCleanup() ); -SHOW_EXPR(watch(pool).size()) -SHOW_EXPR(watch(pool).getMaxScore()) -SHOW_EXPR(watch(pool).getTurnover()) -SHOW_EXPR(show(globalPool)); + removed = pool.cleanup(0.5, pushMem); + CHECK (3 == removed); + CHECK (show(globalPool) == "11, 12, 21, 33, 31, 13"_expect); + + CHECK ( 1 == watch(pool).size() ); + CHECK (10 == watch(pool).getMaxScore() ); + CHECK ( 1 == watch(pool).getTurnover() ); } }; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 0010aed4f..99fdbfe15 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -145325,7 +145325,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -145412,7 +145412,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -145482,9 +145482,9 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - - + + + @@ -145668,9 +145668,13 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + + - + + + + @@ -145688,8 +145692,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - + + @@ -145755,15 +145759,18 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + - + + +

das hier ist keine generische Library, sondern ein Application-Framework. Auch »Vault« ist inhaltlich genau auf Lumiera ausgerichtet, der Unterschied liegt nur im Grad der Integration mit Application-level Services

-
+ +
@@ -147000,8 +147007,9 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - + + + @@ -147037,8 +147045,23 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - + + + + + + + + + +

+ Wenn man eine größere Kapazität reserviert, aber dann wiederholt immer nur einzelne Buffer belegt und wieder freigibt, sollte relativ bald der lokale Pool getrimmt werden, und die freigegebenen Buffer an den globaeln Pool zurückgessend +

+ + +
+ +