From d37a3abd6cb00c830dcca1ea8dcb64ca19fd9869 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 29 Sep 2023 19:21:28 +0200 Subject: [PATCH] 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 --- tests/library/thread-wrapper-test.cpp | 134 +++++++++++++------------- wiki/thinkPad.ichthyo.mm | 32 +++++- 2 files changed, 96 insertions(+), 70 deletions(-) diff --git a/tests/library/thread-wrapper-test.cpp b/tests/library/thread-wrapper-test.cpp index 32fd48566..38ded9666 100644 --- a/tests/library/thread-wrapper-test.cpp +++ b/tests/library/thread-wrapper-test.cpp @@ -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 #include @@ -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 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 ("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 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::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::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 } }; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 3d30d1bcd..69d813c69 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -79951,8 +79951,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + @@ -79971,6 +79971,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + @@ -79988,6 +79991,31 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + +

+ Da ich jetzt einen einfachsten Fall habe, kann der eigentliche Test doch wieder etwas komplexer sein... +

+
    +
  • + in jeden Thread ist eine zufällige Verzögerung eingebaut (1µs < 1ms) +
  • +
  • + mache ein Benchmark über das Starten-warten-Prüfen +
  • +
  • + die durchschnittliche Zeit muß kleiner sein, als die sequentielle Ausführung aller durchschinittlichen Wartezeiten +
  • +
+ + +
+ +