lumiera_documentation/tests/lib/local-slice-test.cpp
Ichthyostega 9bd3e550dc BufferProvider: improve configuration of thread-local dispatch
Since thread_local implies a static setup, we need a way how to
build a new service instance. Usually this would be a default construction,
yet there might be services with special init requirements.

Using an explicit template parameter for this setup configuration
allows to make the primary template parameter just the service itself,
which makes the default usage much clearer.
2026-04-18 16:25:14 +02:00

197 lines
6 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
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;
}
};
using DummySlice = LocalSlice<DummyBackend>;
/**
* 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