Library: actually verify parallelism

Now the ThreadWrapper_test offers both

- a really simple usage example

- a comprehensive test to verify that actually the
  thread-function is invoked the expected number of times
  and that this invocations must have been parallelised
This commit is contained in:
Fischlurch 2023-09-29 19:21:28 +02:00
parent 1d30d47b9a
commit d37a3abd6c
2 changed files with 96 additions and 70 deletions

View file

@ -26,10 +26,10 @@
#include "lib/test/run.hpp"
#include "lib/test/test-helper.hpp"///////////TODO
#include "lib/thread.hpp"
#include "lib/iter-explorer.hpp"
#include "lib/scoped-collection.hpp"
#include "lib/test/microbenchmark.hpp"
#include <atomic>
#include <chrono>
@ -43,47 +43,21 @@ using std::chrono::microseconds;
namespace lib {
namespace test {
namespace test{
namespace { // private test classes and data...
namespace { // test parameters
const uint NUM_THREADS = 200;
struct TestThread
: Thread
{
using Thread::Thread;
uint local{0};
void
doIt (uint a, uint b) ///< the actual operation running in a separate thread
{
uint sum = a + b;
sleep_for (microseconds{sum});
local = sum;
}
};
} // (End) test classes and data....
const uint REPETITIONS = 10;
}
/**********************************************************************//**
* @test use the Lumiera Vault to create some new threads, utilising the
* lumiera::Thread wrapper for binding to an arbitrary operation
* and passing the appropriate context.
*
* @see vault::Thread
* @see threads.h
/*******************************************************************//**
* @test use the lib::Thread wrapper for simplified definition of the
* thread-function, argument binding and starting of threads.
* @see thread.hpp
* @see ThreadWrapperJoin_test
* @see SyncLocking_test
*/
class ThreadWrapper_test : public Test
{
@ -95,54 +69,78 @@ namespace lib {
verifyConcurrentExecution();
}
/**
* @test demonstrate simple usage of the thread-wrapper
*/
/** @test demonstrate simple usage of the thread-wrapper a λ-binding */
void
demonstrateSimpleUsage()
{
lib::ScopedCollection<Thread> threads{NUM_THREADS};
atomic_uint i{0};
Thread thread("counter", [&]{ ++i; }); // bind a λ and launch thread
while (thread) yield(); // ensure thread has finished and detached
atomic_uint invocations{0};
for (uint i=0; i<NUM_THREADS; ++i)
threads.emplace<Thread> ("counter"
,[&]{ ++invocations; });
while (explore(threads).has_any())
yield();
CHECK (invocations == NUM_THREADS);
CHECK (i == 1); // verify the effect has taken place
}
/**
* @test verify the thread function is actually performed concurrently
* - use a derived Thread object, also holding a local data field
* - the thread function sleeps, and then stores the sum of two numbers
* - demonstrate that each instance can have a different argument binding
* - verify each thread function has actually been invoked once per thread,
* by comparing a local sum with values collected from the Thread objects,
* - moreover measure the overall time required for launching the threads
* and then waiting for all threads to have terminated and detached;
* this time must be _shorter_ than all the _average_ sleep times
* compounded (as if the function was invoked sequentially).
*/
void
verifyConcurrentExecution()
{
struct TestThread
: Thread
{
using Thread::Thread;
uint local{0};
void
doIt (uint a, uint b) ///< the actual operation running in a separate thread
{
uint sum = a + b;
sleep_for (microseconds{sum}); // Note: explicit random delay before local store
local = sum;
}
};
// prepare Storage for these objects (not created yet)
lib::ScopedCollection<TestThread> threads{NUM_THREADS};
size_t globalSum = 0;
for (uint i=1; i<=NUM_THREADS; ++i)
{
uint x = rand() % 1000;
globalSum += (i + x);
threads.emplace<TestThread> (&TestThread::doIt, uint{i}, uint{x});
}
while (explore(threads).has_any())
yield();
size_t checkSum = 0;
for (auto& t : threads)
{
CHECK (not t);
CHECK (0 < t.local);
checkSum += t.local;
}
CHECK (checkSum == globalSum);
size_t globalSum = 0;
auto launchThreads = [&]
{
for (uint i=1; i<=NUM_THREADS; ++i)
{
uint x = rand() % 1000;
globalSum += (i + x);
threads.emplace<TestThread> (&TestThread::doIt, uint{i}, uint{x});
} // Note: bind to member function, copying arguments
while (explore(threads).has_any())
yield(); // wait for all threads to have detached
for (auto& t : threads)
{
CHECK (0 < t.local);
checkSum += t.local;
}
};
double runTime = benchmarkTime (launchThreads, REPETITIONS);
CHECK (checkSum == globalSum); // sum of precomputed random numbers matches sum from threads
CHECK (runTime < NUM_THREADS * 1000/2); // random sleep time should be > 500ms on average
}
};

View file

@ -79951,8 +79951,8 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1695394205547" ID="ID_1719816317" MODIFIED="1695394259544" TEXT="Tests umstellen und modernisieren">
<icon BUILTIN="flag-yellow"/>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1695394270093" ID="ID_585437655" MODIFIED="1696002274251" TEXT="ThreadWrapper_test">
<icon BUILTIN="pencil"/>
<node COLOR="#338800" CREATED="1695394270093" ID="ID_585437655" MODIFIED="1696007768225" TEXT="ThreadWrapper_test">
<icon BUILTIN="button_ok"/>
<node COLOR="#338800" CREATED="1695911786871" ID="ID_1371513124" MODIFIED="1695911794232" TEXT="l&#xe4;uft nach Umstellung">
<icon BUILTIN="button_ok"/>
</node>
@ -79971,6 +79971,9 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</html></richcontent>
<arrowlink COLOR="#fedec1" DESTINATION="ID_1532807851" ENDARROW="Default" ENDINCLINATION="416;-16;" ID="Arrow_ID_1646166026" STARTARROW="None" STARTINCLINATION="479;36;"/>
</node>
<node COLOR="#338800" CREATED="1696007772427" ID="ID_1815976624" MODIFIED="1696007791627" TEXT="einfachste Verwendung zeigen">
<icon BUILTIN="button_ok"/>
</node>
<node COLOR="#338800" CREATED="1695394519627" ID="ID_776125132" MODIFIED="1696002223375" TEXT="testen: individuelle Argument&#xfc;bergabe">
<icon BUILTIN="button_ok"/>
</node>
@ -79988,6 +79991,31 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</richcontent>
<icon BUILTIN="button_ok"/>
</node>
<node COLOR="#338800" CREATED="1696007745882" ID="ID_523093343" MODIFIED="1696007928650" TEXT="Zeitmessung &#x27f9; Beweis der Parallelisierung">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
Da ich jetzt einen einfachsten Fall habe, kann der eigentliche Test doch wieder etwas komplexer sein...
</p>
<ul>
<li>
in jeden Thread ist eine zuf&#228;llige Verz&#246;gerung eingebaut (1&#181;s &lt; 1ms)
</li>
<li>
mache ein Benchmark &#252;ber das Starten-warten-Pr&#252;fen
</li>
<li>
die <i>durchschnittliche</i>&#160;Zeit mu&#223; kleiner sein, als die sequentielle Ausf&#252;hrung aller durchschinittlichen Wartezeiten
</li>
</ul>
</body>
</html>
</richcontent>
<icon BUILTIN="button_ok"/>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1695394665822" ID="ID_881973960" MODIFIED="1695394668399" TEXT="ThreadWrapperJoin_test">
<icon BUILTIN="flag-yellow"/>