diff --git a/src/lib/local-slice.hpp b/src/lib/local-slice.hpp new file mode 100644 index 000000000..83855218d --- /dev/null +++ b/src/lib/local-slice.hpp @@ -0,0 +1,77 @@ +/* + LOCAL-SLICE.hpp - access front-end to thread-local instances + + 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 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 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_*/ diff --git a/tests/12metaprogramming.tests b/tests/12metaprogramming.tests index eb74bc770..c7d9155b5 100644 --- a/tests/12metaprogramming.tests +++ b/tests/12metaprogramming.tests @@ -269,11 +269,6 @@ return: 0 END -TEST "Linked iterable elements" LinkedElements_test < + +  **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; + + + /** + * 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 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" < (testThreadLocal, REPETITIONS_BENCH); + cout << "thread-local dispatch: "< (testLocalInc, REPETITIONS_BENCH); + cout << "increment local var : "< t2); + } + + }; + + + /** Register this test class... */ + LAUNCHER(LocalSlice_test, "unit common"); + + +}} // namespace lib::test diff --git a/tests/lib/thread-wrapper-lifecycle-test.cpp b/tests/lib/thread-wrapper-lifecycle-test.cpp index 4429e3676..e25e1c9bb 100644 --- a/tests/lib/thread-wrapper-lifecycle-test.cpp +++ b/tests/lib/thread-wrapper-lifecycle-test.cpp @@ -17,15 +17,14 @@ #include "test/run.hpp" -#include "test/tracking-dummy.hpp" #include "lib/thread.hpp" +#include "lib/iter-explorer.hpp" #include #include #include 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 diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 15cd42299..2b4513227 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -144086,6 +144086,14 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) + + + + + + + + @@ -144195,10 +144203,17 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + + + + + + - + + + @@ -144206,11 +144221,16 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + + - - - + + + + + + + @@ -144253,8 +144273,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - + + @@ -144273,6 +144293,62 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) + + + + + + +

+ Name: LocalSlice<CONF> +

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

+ wir wollen nicht die Contention des OS-Schedulers messen +

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + @@ -144312,14 +144388,25 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + - - + + + + + + + + + + + + +