From fd0e62076a7dc55eb1a43731d17056a8cc7094e9 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 29 May 2026 18:45:01 +0200 Subject: [PATCH] BufferProvider: perform a successful stress-test Yet only in a simple way, by pressing with 100 threads.... This test runs for about 10 seconds, shows a massive allocation of more than 1 GiB, but comes down clean, without deadlocks, using all cores and returning all memory blocks at the end. This test uncovered some missing safety checks, and it highlighted (again) that this memory management scheme is elaborate and potentially fragile: I had to deal with carried-on state in the local metadata registry associated with the main thread (in fact I did not ever want that registry to be used, but turns out that the initialisation check, that runs in the constructor, already pulls up an instance for the main thread, which carries stale state after the test case is complete) --- src/lib/scoped-collection.hpp | 2 +- src/vault/mem/buffer-provider.cpp | 3 + tests/vault/mem/buffer-provider-load-test.cpp | 61 ++++-- wiki/thinkPad.ichthyo.mm | 190 +++++++++++++++++- 4 files changed, 240 insertions(+), 16 deletions(-) diff --git a/src/lib/scoped-collection.hpp b/src/lib/scoped-collection.hpp index 3934be30d..a53e18e79 100644 --- a/src/lib/scoped-collection.hpp +++ b/src/lib/scoped-collection.hpp @@ -213,7 +213,7 @@ namespace lib { /* == some pre-defined Builders == */ - /** Builder: ills the given storage slot with a I-instance + /** Builder: fills the given storage slot with a I-instance * constructed from copies of the given arguments. * @return builder-λ to pass into ScopeCollection's ctor. */ template diff --git a/src/vault/mem/buffer-provider.cpp b/src/vault/mem/buffer-provider.cpp index 4cf7ff8d9..478fe212b 100644 --- a/src/vault/mem/buffer-provider.cpp +++ b/src/vault/mem/buffer-provider.cpp @@ -78,6 +78,9 @@ namespace mem { BufferProvider::getBufferSize (HashVal id) const { auto& key = bufferStage_->lookup (id); + if (metadata::Key::INVALID == key) + throw err::State ("encountered unknown buffer type (stale buffer descriptor?" + ,LUMIERA_ERROR_BUFFER_MANAGEMENT); return key.storageSize(); } diff --git a/tests/vault/mem/buffer-provider-load-test.cpp b/tests/vault/mem/buffer-provider-load-test.cpp index 60c4c0ec4..1aa211702 100644 --- a/tests/vault/mem/buffer-provider-load-test.cpp +++ b/tests/vault/mem/buffer-provider-load-test.cpp @@ -26,7 +26,7 @@ #include "vault/gear/test-chain-load.hpp" #include "lib/scoped-collection.hpp" #include "lib/depend-inject.hpp" -//#include "lib/iter-explorer.hpp" +#include "lib/iter-explorer.hpp" #include "lib/sync-barrier.hpp" #include "lib/iter-stack.hpp" #include "lib/thread.hpp" @@ -38,10 +38,10 @@ //#include #include -//using lib::explore; +using lib::Random; using lib::Depend; using lib::DependInject; -using lib::Random; +using lib::explore; //using lib::Literal; using lib::Thread; using lib::SyncBarrier; @@ -59,7 +59,7 @@ namespace test { namespace { // ========== Test Setup ========== - const size_t NUM_THREADS = 100; ///< press concurrently + const size_t MANY_THREADS = 100; ///< press concurrently const size_t MAX_SCALE = 5; ///< maximum scale step for randomised buffer types @@ -107,6 +107,7 @@ namespace test { { static Depend bufferProvider; static BuffTypes buffType_; + static size_t calibrated_; // install mock instances for the relevant services DependInject::Local metaHub; @@ -116,6 +117,7 @@ namespace test { // static storage for the mock-engine entrance point Depend Ctx::bufferProvider; BuffTypes Ctx::buffType_{MAX_SCALE}; + size_t Ctx::calibrated_{0}; } @@ -156,6 +158,7 @@ namespace test { workActivity() { SyncBarrier::sync(); +cout<<"Start:"< metaHub; + return metaHub().isKnown (typeID); + }; if (Ctx::buffType_.empty() - or Ctx::buffType_[0].buffSize() != CONF::BASE_BUFFSIZ ) + or not isKnown (Ctx::buffType_[0])) { - load_.calibrate(); +cout<<"init-types"<>; + Threads threads{MANY_THREADS, Threads::fill()}; + + // wait for all threads to finish... + while (explore(threads).has_any()) + sleep_for (5ms); + +SHOW_EXPR("harumpfha") +SHOW_EXPR(mockEngine.metaHub->cntEntries()) +SHOW_EXPR(watch(*mockEngine.globalPool).numAllocs()) + + sleep_for (500us); + mockEngine.globalPool->processPendingRequests(); +SHOW_EXPR(watch(*mockEngine.globalPool).bytesLeased()); + } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 9f1dfdf7e..a6151fee5 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -148740,8 +148740,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) Für das korrekte Test-Setup ist es lediglich erforderlich, daß effektiv die instrumentierung stattfindet bevor der jeweilige Test-Worker gestartet wird.

- - + @@ -148850,6 +148849,193 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ etwas anderes ist an der Stelle auch relativ unwahrscheinlich: die BASE_BUFFSIZ steckt ja eben grade statisch in der jeweilgen Thread-Klasse. Das würde nur abweichen, wenn man im gleichen Testfall mit mehreren verschiedenen Thread-Klassen operiert +

+ + +
+ + + + + + + +

+ lookup liefert nicht Key::INVALID +

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

+ ...und das wird auch so bleiben, denn das ist TESTCODE +

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

+ Dann bricht dieser ganze Caching-Mechanismus mit den lokalen Pools zusammen, denn eine solche Löschung müßte global überall propagiert werden +

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

+ das verwirrt nur und trägt nichts zum Verständnis des gesamten Konstrukts bei... +

+ +
+
+ + + + +

+ Damit umgehen wir die verschleppten Daten, obwohl sie nun mal schon da sind. EngineMetadata ist ja eine neue Mock-Instanz, und daher leer. Im nächsten Schritt gehen wir dann zwar zur Registrierung der Buffer-Typen wieder den kanonischen Weg, und fragen deshalb auch wieder den lokalen Pool — aber zum Glück hab ich die LocalBufferStage::defineBufferType() so geschrieben, daß sie stillschweigend einen identisch so bestehenden Eintrag weiterverwendet. Ist auch sinnvoll, aufgrund der Hash-ID. Und zudem wird beim Erstellen eines neuen Typs grundsätzlich nach oben synchronisiert, und damit sind wir dann wieder sauber. +

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

+ das ist ein sehr gutes Zeichen, denn es bedeutet, daß sich überschüssige Allokationen in den lokalen Pools ausbalanciert haben +

+ +
+ +
+ + + + +

+ ...was zu erwarten ist, bedingt durch die massive Überprovisionierung, kombiniert mit den sleep-delays +

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

+ das ist eine informelle Policy, die ich seit langem verfolge: wenn es um quantitative Last/Performance-Messungen geht, setze ich nur 4 Kerne ein, damit die Testsuite auch auf kleineren Maschinen noch brauchbare Ergebnisse liefert. +

+ +
+ +
+ + + + +