BufferProvider: skeleton for thread-local service dispatch

- Use a ''monostate'' as access point to thread-local service instances
 - rely on a configuration-context to setup the actual service
 - investigate runtime overhead using a multithreaded microbenchmark
This commit is contained in:
Fischlurch 2026-04-17 23:40:37 +02:00
parent 8d563d1ea9
commit a06ef95e94
6 changed files with 389 additions and 20 deletions

77
src/lib/local-slice.hpp Normal file
View file

@ -0,0 +1,77 @@
/*
LOCAL-SLICE.hpp - access front-end to thread-local instances
Copyright (C)
2026, Hermann Vosseler <Ichthyostega@web.de>
  **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 local-slice.hpp
** Front-end to attach to a set of thread-local instances.
** This setup helps to adapt a given service implementation to be deployed
** as per-thread instances, operating in a massively concurrent situation.
** The services installed this way must be able to work as self-contained
** instances transparently, possibly cooperating with a central hub behind
** the scenes, which however must be coordinated by the services themselves.
**
** Since thread-local storage is effectively static, the configuration of
** instances happens at some point _prior_ to the first use from within a
** thread, and otherwise not further specified. The LocalSlice access front-end
** is a _monostate_ and all instances thus expose the same set of per-thread
** service instances, which are created on-demand (by the C++ runtime system).
**
** @warning as of 2022 the implementation of `thread_local` in the GNU runtime
** was observed to incur seemingly unnecessary overhead, which however
** is relevant only in a high-performance environment.
** @todo WIP-WIP 4/2026 prototyping an Engine Buffer manager /////////////////////////////////////////////TICKET #1425
** @see LocalSlice_test
** @see ////TODO usage example
**
*/
#ifndef LIB_LOCAL_SLICE_H_
#define LIB_LOCAL_SLICE_H_
#include "lib/integral.hpp"
namespace lib {
/**
* Common access point to thread-local service instances.
* @tparam CONF static config / setup to initialise the service.
*/
template<class CONF>
class LocalSlice
{
using Service = CONF::Service;
public:
LocalSlice() = default;
// using default copy/assignment
/** service access */
Service&
operator*() const
{
thread_local Service srvInstance{CONF::init()};
return srvInstance;
}
Service*
operator->() const
{
return & LocalSlice::operator*();
}
};
} // namespace lib
#endif /*LIB_LOCAL_SLICE_H_*/

View file

@ -269,11 +269,6 @@ return: 0
END
TEST "Linked iterable elements" LinkedElements_test <<END
return: 0
END
TEST "Polymorphic value objects" PolymorphicValue_test <<END
return: 0
END

View file

@ -473,6 +473,15 @@ TEST "Lazy init of a functor" LazyInit_test <<END
END
TEST "Intrusive linked elements" LinkedElements_test <<END
return: 0
END
TEST "Thread-local service instances" LocalSlice_test <<END
END
TEST "configurable Factory" MultiFact_test <<END
END

View file

@ -0,0 +1,204 @@
/*
LocalSlice(Test) - verify access to thread-local services
Copyright (C)
2026, Hermann Vosseler <Ichthyostega@web.de>
  **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 local-slice-test.cpp
** unit test \ref LocalSlice_test
*/
#include "test/run.hpp"
#include "test/test-helper.hpp"
#include "lib/scoped-collection.hpp"
#include "test/microbenchmark.hpp"
#include "lib/iter-explorer.hpp"
#include "lib/format-cout.hpp"
#include "lib/thread.hpp"
#include "lib/local-slice.hpp"
using lib::Thread;
using lib::explore;
using test::threadBenchmark;
using std::this_thread::yield;
using std::this_thread::sleep_for;
using std::chrono_literals::operator ""us;
namespace lib {
namespace test {
namespace { // test fixture...
const uint NUM_THREADS = 200;
constexpr size_t CONCURR_BENCH = 4;
const size_t REPETITIONS_BENCH = 1'000'000;
/**
* the "service" to be instantiated thread-local
*/
class DummyBackend
{
uint step{0};
public:
uint
getStep()
{
return ++step;
}
};
struct DummySetup
{
using Service = DummyBackend;
static Service init() { return Service(); }
};
using DummySlice = LocalSlice<DummySetup>;
/**
* A common shared service instance
* that dispatches through LocalSlice
* into the thread-local service back-ends
*/
class DummyFrontend
{
DummySlice dummySrv_;
public:
uint
invoke()
{
return dummySrv_->getStep()
+ dummySrv_->getStep()
+ dummySrv_->getStep()
+ dummySrv_->getStep()
;
}
};
//
} //(End) test fixture
/***********************************************************************//**
* @test demonstrate access to thread-local service instances.
* - setup a common front-end service instance
* - its functionality is implemented by delegating
* through the LocalSlice into the thread-local back-ends
* - launch threads and invoke the front-end concurrently
* @see local-slice.hpp
*/
class LocalSlice_test : public Test
{
virtual void
run (Arg)
{
simpleUsage();
investigatePerformance();
}
/** @test access thread-local service back-ends
* through a shared front-end instance;
* verify that each thread invokes a
* separate instance without interference
* @see ThreadWrapper_test
*/
void
simpleUsage ()
{
// Σi ≡ N/2·(N+1)
auto expectSum = [](uint n){ return n/2 * (n+1); };
// the front-end invokes the back-end four times in row...
uint i1 = expectSum(4);
uint i2 = expectSum(8) - expectSum(4);
uint i3 = expectSum(12) - expectSum(8);
// Note: all threads use this common service front-end instance
DummyFrontend myDummy;
// Now invoke through this front-end
CHECK (i1 == myDummy.invoke());
CHECK (i2 == myDummy.invoke());
// launch a significant number of threads,
// each verifying the same invocation sequence
lib::ScopedCollection<Thread> threads{NUM_THREADS};
for (uint i=1; i<=NUM_THREADS; ++i)
threads.emplace ([&]{
CHECK (i1 == myDummy.invoke());
sleep_for (100us);
CHECK (i2 == myDummy.invoke());
sleep_for (100us);
CHECK (i3 == myDummy.invoke());
});
while (explore(threads).has_any())
yield(); // wait for all threads to finish
// ...without any interference with the main thread's instance
CHECK (i3 == myDummy.invoke());
}
/** @test investigate performance of thread-local dispatch
* - subject-1 accesses the increment through the common thread-local dispatch
* - subject-2 increments just a local variable in each thread
* - run a multithreaded benchmark with 1 million repetitions
* - but only small number of threads to avoid OS scheduler congestion
* @see test::threadBenchmark()
*/
void
investigatePerformance()
{
DummySlice threadLocalBackend;
auto testThreadLocal = [&]{
auto volatile result = threadLocalBackend->getStep();
return result;
};
auto testLocalInc = [cnt=0] mutable
{
auto volatile result = ++cnt;
return result;
};
cout << "µ-benchmark :: thread-local" <<endl;
auto [t1, s1] = ::test::threadBenchmark<CONCURR_BENCH> (testThreadLocal, REPETITIONS_BENCH);
cout << "thread-local dispatch: "<<t1<<"µs" <<endl;
auto [t2, s2] = ::test::threadBenchmark<CONCURR_BENCH> (testLocalInc, REPETITIONS_BENCH);
cout << "increment local var : "<<t2<<"µs" <<endl;
CHECK (s1 == s2);
CHECK (t1 > t2);
}
};
/** Register this test class... */
LAUNCHER(LocalSlice_test, "unit common");
}} // namespace lib::test

View file

@ -17,15 +17,14 @@
#include "test/run.hpp"
#include "test/tracking-dummy.hpp"
#include "lib/thread.hpp"
#include "lib/iter-explorer.hpp"
#include <atomic>
#include <chrono>
#include <memory>
using test::Test;
using test::Dummy;
using lib::explore;
using std::atomic_uint;
using std::this_thread::yield;
@ -138,8 +137,6 @@ namespace test{
sleep_for (5ms);
}
};
// Note the Dummy member allows to watch instance lifecycle
CHECK (0 == Dummy::checksum());
// the frontEnd allows to access the TestThread component
// and also represents the running state

View file

@ -144086,6 +144086,14 @@ StM_bind(Builder&lt;R1&gt; b1, Extension&lt;R1,R2&gt; extension)
<node CREATED="1776296068447" ID="ID_882324053" LINK="https://alexander-ostrovskiy-c.co.uk/posts/thread-local-storage-in-c-best-practices-and-pitfalls/" MODIFIED="1776299538975" TEXT="Practical usage and hidden risks"/>
<node BACKGROUND_COLOR="#fafe99" COLOR="#fa002a" CREATED="1776296046515" ID="ID_962020738" LINK="https://yosefk.com/blog/cxx-thread-local-storage-performance.html" MODIFIED="1776299543765" TEXT="Thread-local Performance">
<icon BUILTIN="broken-line"/>
<node BACKGROUND_COLOR="#f0d5c5" COLOR="#990033" CREATED="1776475228247" ID="ID_566732298" MODIFIED="1776476062001" TEXT="besteht dieses Problem noch?">
<arrowlink COLOR="#5423b6" DESTINATION="ID_751382987" ENDARROW="Default" ENDINCLINATION="918;0;" ID="Arrow_ID_1654719917" STARTARROW="None" STARTINCLINATION="897;75;"/>
<icon BUILTIN="help"/>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#172ce6" CREATED="1776475271089" ID="ID_622277074" MODIFIED="1776475364341" TEXT="mein Microbenchmark sieht nicht so schlimm aus">
<linktarget COLOR="#515284" DESTINATION="ID_622277074" ENDARROW="Default" ENDINCLINATION="296;0;" ID="Arrow_ID_1814168977" SOURCE="ID_580360080" STARTARROW="Default" STARTINCLINATION="296;0;"/>
<font NAME="SansSerif" SIZE="11"/>
</node>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#f0d5c5" COLOR="#990033" CREATED="1776299548025" ID="ID_410006825" MODIFIED="1776299559493" TEXT="ist das jetzt schlimmmmm?">
@ -144195,10 +144203,17 @@ StM_bind(Builder&lt;R1&gt; b1, Extension&lt;R1,R2&gt; extension)
</body>
</html></richcontent>
<icon BUILTIN="forward"/>
<node CREATED="1776359147782" ID="ID_49027423" MODIFIED="1776359159216" TEXT="als notieren wir dieses potentielle Problem"/>
<node CREATED="1776359147782" ID="ID_49027423" MODIFIED="1776359159216" TEXT="als notieren wir dieses potentielle Problem">
<node BACKGROUND_COLOR="#d2beaf" COLOR="#5c4d6e" CREATED="1776475337836" ID="ID_580360080" LINK="https://issues.lumiera.org/ticket/1427" MODIFIED="1776475378273" TEXT="#1427 performance-impact of thread_local ">
<arrowlink COLOR="#515284" DESTINATION="ID_622277074" ENDARROW="Default" ENDINCLINATION="296;0;" ID="Arrow_ID_1814168977" STARTARROW="Default" STARTINCLINATION="296;0;"/>
<icon BUILTIN="hourglass"/>
</node>
</node>
<node CREATED="1776359159957" ID="ID_611795063" MODIFIED="1776359168445" TEXT="untersuchen es ggfs. sp&#xe4;ter empirisch"/>
<node CREATED="1776359169275" ID="ID_1606858571" MODIFIED="1776359191747" TEXT="und bauen notfalls ein &#xbb;ThreadLocal&#xab;, das optimal effizient ist"/>
<node CREATED="1776359203560" ID="ID_240001081" MODIFIED="1776359249485" TEXT="&#x27f9; den aktuellen Entwruf unter dieser Ma&#xdf;gabe weiterentwickeln">
<node BACKGROUND_COLOR="#ccb59b" COLOR="#6e2a38" CREATED="1776359203560" ID="ID_240001081" MODIFIED="1776477644814" TEXT="&#x27f9; den aktuellen Entwruf unter dieser Ma&#xdf;gabe weiterentwickeln">
<font ITALIC="true" NAME="SansSerif" SIZE="14"/>
<icon BUILTIN="yes"/>
<node CREATED="1776359250702" ID="ID_1110100689" MODIFIED="1776359257699" TEXT="er ist klar strukturiert"/>
<node CREATED="1776359258534" ID="ID_193416980" MODIFIED="1776359267522" TEXT="er enth&#xe4;lt ein Element der Eleganz"/>
<node CREATED="1776359271069" ID="ID_859503094" MODIFIED="1776359280656" TEXT="er ist noch nicht optimiert (was gut ist)"/>
@ -144206,11 +144221,16 @@ StM_bind(Builder&lt;R1&gt; b1, Extension&lt;R1,R2&gt; extension)
</node>
</node>
</node>
<node CREATED="1776204526672" ID="ID_961403309" MODIFIED="1776204711931" TEXT="Prototyping (testgetrieben)">
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1776204526672" ID="ID_961403309" MODIFIED="1776477618192" TEXT="Prototyping (testgetrieben)">
<icon BUILTIN="pencil"/>
<node CREATED="1776204565654" ID="ID_45614742" MODIFIED="1776204569140" TEXT="generischer Slice">
<node CREATED="1776292208608" ID="ID_1141218614" MODIFIED="1776292245482" TEXT="Zweck: Anbinden eines Slice in einem normalen Objekt-Kontext">
<node CREATED="1776292987880" ID="ID_808037956" MODIFIED="1776292998479" TEXT="Zugriff &#xfc;ber Pointer-artige Syntax"/>
<node CREATED="1776293021744" ID="ID_808171568" MODIFIED="1776293034905" TEXT="Problem: thread_local ist implizit statisch">
<node CREATED="1776292208608" ID="ID_1141218614" MODIFIED="1776477482889" TEXT="Zweck: Anbinden eines Slice in einem normalen Objekt-Kontext">
<icon BUILTIN="info"/>
<node CREATED="1776292987880" ID="ID_808037956" MODIFIED="1776477504910" TEXT="Zugriff &#xfc;ber Pointer-artige Syntax">
<icon BUILTIN="yes"/>
</node>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1776293021744" ID="ID_808171568" MODIFIED="1776477489785" TEXT="Problem: thread_local ist implizit statisch">
<icon BUILTIN="messagebox_warning"/>
<node CREATED="1776293040334" ID="ID_555811092" MODIFIED="1776293060463" TEXT="das hei&#xdf;t es ist eine &#xbb;versteckte globale Variable&#xab;"/>
<node CREATED="1776295872712" ID="ID_906429004" MODIFIED="1776295889501" TEXT="stellt das f&#xfc;r den konkteten Use-Case &#xfc;berhaupt eine Beschr&#xe4;nkung dar?">
<node CREATED="1776295891705" ID="ID_877250740" MODIFIED="1776295921697" TEXT="ja &#x2014; eine hexagonale Beschr&#xe4;nkung: &#x27f9; man kann&apos;s nur einmal Unit-testen"/>
@ -144253,8 +144273,8 @@ StM_bind(Builder&lt;R1&gt; b1, Extension&lt;R1,R2&gt; extension)
</html></richcontent>
</node>
</node>
<node CREATED="1776442726252" ID="ID_1448305654" MODIFIED="1776442778834" TEXT="dadurch w&#xfc;rde &#xbb;automatisch&#xab; eine einzige Metadata-Storage im System entstehen">
<linktarget COLOR="#fefdd2" DESTINATION="ID_1448305654" ENDARROW="Default" ENDINCLINATION="480;17;" ID="Arrow_ID_972765157" SOURCE="ID_1548755064" STARTARROW="None" STARTINCLINATION="469;0;"/>
<node CREATED="1776442726252" ID="ID_1448305654" MODIFIED="1776477701154" TEXT="dadurch w&#xfc;rde &#xbb;automatisch&#xab; eine einzige Metadata-Storage im System entstehen">
<linktarget COLOR="#fefdd2" DESTINATION="ID_1448305654" ENDARROW="Default" ENDINCLINATION="480;17;" ID="Arrow_ID_972765157" SOURCE="ID_1548755064" STARTARROW="None" STARTINCLINATION="613;24;"/>
<icon BUILTIN="idea"/>
<node CREATED="1776442794218" HGAP="25" ID="ID_1105253979" MODIFIED="1776442814861" TEXT="man m&#xfc;&#xdf;te dann nur noch OutputSlot &#xbb;einfangen&#xab;" VSHIFT="-3"/>
</node>
@ -144273,6 +144293,62 @@ StM_bind(Builder&lt;R1&gt; b1, Extension&lt;R1,R2&gt; extension)
<node CREATED="1776438572468" ID="ID_1804894411" MODIFIED="1776438590402" TEXT="die einzelne Instanz ist Monostate und default-konstruierbar und kopierbar"/>
</node>
</node>
<node COLOR="#338800" CREATED="1776461384382" ID="ID_43420839" MODIFIED="1776477478439" TEXT="Implementierung / Test">
<icon BUILTIN="button_ok"/>
<node COLOR="#435e98" CREATED="1776461548757" ID="ID_1399164537" MODIFIED="1776477531388">
<richcontent TYPE="NODE"><html>
<head/>
<body>
<p>
<b>Name</b>: <font face="Monospaced">LocalSlice&lt;CONF&gt;</font>
</p>
</body>
</html></richcontent>
</node>
<node CREATED="1776474854867" ID="ID_1487038882" MODIFIED="1776474859837" TEXT="CONF">
<node CREATED="1776474861216" ID="ID_399712480" MODIFIED="1776474870435" TEXT="typedef Service"/>
<node CREATED="1776474871327" ID="ID_142831800" MODIFIED="1776474879674" TEXT="eine init-Funktion"/>
</node>
<node COLOR="#338800" CREATED="1776474884669" ID="ID_66962199" MODIFIED="1776477543983" TEXT="LocalSlice_test">
<icon BUILTIN="button_ok"/>
<node CREATED="1776474892988" ID="ID_1841154811" MODIFIED="1776474911245" TEXT="verwende ein Backend das eine Variable inkrementiert"/>
<node CREATED="1776474912122" ID="ID_225188556" MODIFIED="1776474931651" TEXT="verwende einen front-End-Service, der das Backend mehrfach aufruft"/>
<node CREATED="1776474932743" ID="ID_285414025" MODIFIED="1776474959191" TEXT="erst mal im main-Thread: stimmen die produzierten Zahlen?"/>
<node CREATED="1776474960491" ID="ID_670758355" MODIFIED="1776474975933" TEXT="massiv mit parallellen Threads pressen"/>
<node BACKGROUND_COLOR="#a1d4d1" COLOR="#135d40" CREATED="1776474978433" ID="ID_104524737" MODIFIED="1776477604743" TEXT="Benchmark">
<node CREATED="1776474985168" ID="ID_722554158" MODIFIED="1776475006881" TEXT="verwende mein threadBenchmark"/>
<node CREATED="1776475011468" ID="ID_572983472" MODIFIED="1776475941260" TEXT="dieses Mal nur 4 Threads">
<richcontent TYPE="NOTE"><html>
<head/>
<body>
<p>
wir wollen nicht die Contention des OS-Schedulers messen
</p>
</body>
</html></richcontent>
</node>
<node CREATED="1776475054583" ID="ID_139797430" MODIFIED="1776475070624" TEXT="aber eine Million mal in jedem Thread wiederholen"/>
<node CREATED="1776475071885" ID="ID_1243408123" MODIFIED="1776475095349" TEXT="zum Vergleich: ein Lambda das direkt eine lokale Variable im Thread hochz&#xe4;hlt"/>
<node COLOR="#435e98" CREATED="1776475100532" ID="ID_1636417377" MODIFIED="1776475388401" TEXT="Ergebnis">
<font BOLD="true" NAME="SansSerif" SIZE="12"/>
<icon BUILTIN="idea"/>
<node BACKGROUND_COLOR="#e1efac" COLOR="#0155a5" CREATED="1776475103868" ID="ID_751382987" MODIFIED="1776475434097" TEXT="naja ... also soooo dramatisch langsamer ist das nicht">
<linktarget COLOR="#5423b6" DESTINATION="ID_751382987" ENDARROW="Default" ENDINCLINATION="918;0;" ID="Arrow_ID_1654719917" SOURCE="ID_566732298" STARTARROW="None" STARTINCLINATION="897;75;"/>
<icon BUILTIN="help"/>
</node>
<node CREATED="1776475117023" ID="ID_1331151971" MODIFIED="1776475997517" TEXT="mit thread_local : ~ 20ns"/>
<node CREATED="1776475134176" ID="ID_462698106" MODIFIED="1776476001516" TEXT="lokale Variable : ~ 9ns"/>
<node CREATED="1776475155945" ID="ID_4564368" MODIFIED="1776475171818" TEXT="Auch ein nicht-trivialer Konstruktor macht keinen me&#xdf;baren Unterschied"/>
<node CREATED="1776475174183" ID="ID_562193803" MODIFIED="1776475187901" TEXT="und zudem linken wir ja schon in eine Shared-Lib mit -fPIC"/>
<node CREATED="1776476081141" ID="ID_1398683528" MODIFIED="1776476100906" TEXT="Anzahl Threads variieren">
<icon BUILTIN="forward"/>
<node CREATED="1776476103003" ID="ID_1673139479" MODIFIED="1776476126644" TEXT="die thread-local-Variante w&#xe4;chst linear mit der Zahl der Threads"/>
<node CREATED="1776476128271" ID="ID_452771680" MODIFIED="1776476144920" TEXT="die lokale Variable &#xe4;ndert sich fast gar nicht"/>
</node>
</node>
</node>
</node>
</node>
</node>
<node CREATED="1776204569932" ID="ID_1285006287" MODIFIED="1776204574453" TEXT="Stage-Slice"/>
<node CREATED="1776204575221" ID="ID_869340284" MODIFIED="1776204579271" TEXT="Store-Slice"/>
@ -144312,14 +144388,25 @@ StM_bind(Builder&lt;R1&gt; b1, Extension&lt;R1,R2&gt; extension)
<node CREATED="1776442525319" ID="ID_401093527" MODIFIED="1776442557719" TEXT="es gibt nur den einen Aufruf aus der BufferStage-Impl (+Tests)"/>
<node CREATED="1776442560275" ID="ID_993355013" MODIFIED="1776442576727" TEXT="&#x27f9; die FamilyID k&#xf6;nnte auch als Parameter kommen"/>
</node>
<node CREATED="1776442580542" ID="ID_1214945994" MODIFIED="1776442607448" TEXT="es w&#xe4;re ohne Weiteres m&#xf6;glich, die Family-ID in die Buffer-Stage-Implementierung zu heben">
<node BACKGROUND_COLOR="#c8c0b6" COLOR="#435e98" CREATED="1776442580542" ID="ID_1214945994" MODIFIED="1776477692805" TEXT="es w&#xe4;re ohne Weiteres m&#xf6;glich, die Family-ID in die Buffer-Stage-Implementierung zu heben">
<icon BUILTIN="idea"/>
<node CREATED="1776442609827" ID="ID_570121338" MODIFIED="1776442627773" TEXT="damit w&#xe4;re sie aus der &#xbb;Thread-Local-Zone&#xab; raus"/>
<node CREATED="1776442630476" ID="ID_1314697653" MODIFIED="1776442651841" TEXT="und eine einzige BufferMetadata-Storage k&#xf6;nnte alle BufferProvider im Stystem bedienen"/>
<node CREATED="1776442666774" ID="ID_1548755064" MODIFIED="1776442784578" TEXT="das w&#xfc;rde dann sogar implizit passieren &#x2014; weil thread_local &#x27f9; static">
<arrowlink COLOR="#fefdd2" DESTINATION="ID_1448305654" ENDARROW="Default" ENDINCLINATION="480;17;" ID="Arrow_ID_972765157" STARTARROW="None" STARTINCLINATION="469;0;"/>
<node CREATED="1776442666774" ID="ID_1548755064" MODIFIED="1776477701154" TEXT="das w&#xfc;rde dann sogar implizit passieren &#x2014; weil thread_local &#x27f9; static">
<arrowlink COLOR="#fefdd2" DESTINATION="ID_1448305654" ENDARROW="Default" ENDINCLINATION="480;17;" ID="Arrow_ID_972765157" STARTARROW="None" STARTINCLINATION="613;24;"/>
</node>
</node>
<node COLOR="#338800" CREATED="1776477706236" ID="ID_17016961" MODIFIED="1776477711154" TEXT="ges&#xe4;gt ... getan">
<icon BUILTIN="button_ok"/>
</node>
</node>
<node COLOR="#435e98" CREATED="1776477731488" ID="ID_1764139922" MODIFIED="1776477873117" TEXT="Fazit">
<font BOLD="true" NAME="SansSerif" SIZE="12"/>
<node CREATED="1776477743564" ID="ID_893955025" MODIFIED="1776477774653" TEXT="durch Heben in die BufferStage wird BufferMetadata ein beliebig nutzbarer Service"/>
<node CREATED="1776477775546" ID="ID_1413251785" MODIFIED="1776477827799" TEXT="zudem noch Import/Export von Registrierungen &#x27f9; thread-local aufrufbar"/>
<node CREATED="1776477847655" ID="ID_1038306996" MODIFIED="1776477868291" TEXT="einen globalen Metadataten-Store bekommt man so &#xbb;nebenbei geschenkt&#xab;">
<icon BUILTIN="idea"/>
</node>
</node>
</node>
</node>