Library/Application: rework TypedCounter and tests
The existing TypedCounter_test was excessively clever and convoluted, yet failed to test the critical elements systematically. Indeed, two bugs were hidden in synchronisation and instance access. - build a new concurrent test from scratch, now using the threadBenchmark function for the actual concurrent execution and just invoked a random selected access to the counter repeatedly from a large number of threads. - rework the TypedContext and counter to use Atomics where applicable; measurements indicate however that this has only negligible impact on the amortised invocation times, which are around 60ns for single-threaded access, yet can increase by factor 100 due to contention.
This commit is contained in:
parent
80f09cb33b
commit
4f50cbc386
7 changed files with 299 additions and 293 deletions
|
|
@ -62,5 +62,11 @@ namespace idi {
|
|||
} //(End)integration helpers...
|
||||
|
||||
|
||||
TypedCounter&
|
||||
sharedInstanceCounter()
|
||||
{ // Meyer's Singleton
|
||||
static TypedCounter instanceCounter;
|
||||
return instanceCounter;
|
||||
}
|
||||
|
||||
}} // namespace lib::idi
|
||||
|
|
|
|||
|
|
@ -128,6 +128,9 @@ namespace idi {
|
|||
}
|
||||
|
||||
|
||||
TypedCounter& sharedInstanceCounter();
|
||||
|
||||
|
||||
/** build a per-type identifier, with type prefix and running counter.
|
||||
* @return a type based prefix, followed by an instance number
|
||||
* @note we use the short prefix without namespace, not necessarily unique
|
||||
|
|
@ -141,8 +144,7 @@ namespace idi {
|
|||
inline string
|
||||
generateSymbolicID()
|
||||
{
|
||||
static TypedCounter instanceCounter;
|
||||
return format::instance_format (namePrefix<TY>(), instanceCounter.inc<TY>());
|
||||
return format::instance_format (namePrefix<TY>(), sharedInstanceCounter().inc<TY>());
|
||||
}
|
||||
|
||||
/** build a long type based identifier, with running counter and custom prefix.
|
||||
|
|
@ -156,8 +158,7 @@ namespace idi {
|
|||
inline string
|
||||
generateExtendedID(string prefix ="")
|
||||
{
|
||||
static TypedCounter instanceCounter;
|
||||
return format::instance_format (prefix + typeFullID<TY>(), instanceCounter.inc<TY>());
|
||||
return format::instance_format (prefix + typeFullID<TY>(), sharedInstanceCounter().inc<TY>());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -44,7 +44,10 @@
|
|||
** actually to verify this by real measurements (as of 2011)
|
||||
** @todo 2010 ... this is the first, preliminary version of a facility,
|
||||
** which is expected to get quite important for custom allocation management.
|
||||
**
|
||||
** @remark in 2023 changed partially to use atomics; measurements indicate however
|
||||
** that the impact of locking technology is negligible. In concurrent use,
|
||||
** the wait times are dominating. In single threaded use, both using Atomics
|
||||
** or a nonrecursive mutex yield amortised invocation times around 60ns.
|
||||
** @see typed-counter-test.cpp
|
||||
** @see TypedAllocationManager
|
||||
** @see AllocationCluster (custom allocation scheme using a similar idea inline)
|
||||
|
|
@ -59,9 +62,9 @@
|
|||
#include "lib/error.hpp"
|
||||
#include "lib/sync-classlock.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
#include <deque>
|
||||
|
||||
|
||||
namespace util {
|
||||
|
|
@ -72,7 +75,7 @@ namespace lib {
|
|||
|
||||
typedef size_t IxID; //////////////////////TICKET #863
|
||||
|
||||
using std::vector;
|
||||
using std::deque;
|
||||
using std::string;
|
||||
|
||||
|
||||
|
|
@ -135,11 +138,17 @@ namespace lib {
|
|||
|
||||
/**
|
||||
* Utility providing a set of counters, each tied to a specific type.
|
||||
* The actual allocation of id numbers is delegated to TypedContext.
|
||||
* Such a counter is used to build [symbolic instance IDs](\ref lib::meta::generateSymbolicID)
|
||||
* with a per-type running counter. Such IDs are used for lib::idi::EntryID and for lib::diff::GenNode.
|
||||
* @warning the index space for typeIDs is application global; the more distinct types are used, the more
|
||||
* slots will be present in _each instance of TypedCounter._ As of 2023 we are using < 30 distinct
|
||||
* types for these use cases, and thus the wasted memory is not much of a concern.
|
||||
*/
|
||||
class TypedCounter
|
||||
: public Sync<>
|
||||
{
|
||||
mutable vector<long> counters_;
|
||||
mutable deque<std::atomic_int64_t> counters_;
|
||||
|
||||
template<typename TY>
|
||||
IxID
|
||||
|
|
@ -147,42 +156,37 @@ namespace lib {
|
|||
{
|
||||
IxID typeID = TypedContext<TypedCounter>::ID<TY>::get();
|
||||
if (size() < typeID)
|
||||
counters_.resize (typeID);
|
||||
{ // protect against concurrent slot allocations
|
||||
Lock sync(this);
|
||||
if (size() < typeID)
|
||||
counters_.resize (typeID);
|
||||
}
|
||||
|
||||
ENSURE (counters_.capacity() >= typeID);
|
||||
ENSURE (counters_.size() >= typeID);
|
||||
return (typeID - 1);
|
||||
}
|
||||
|
||||
|
||||
public:
|
||||
TypedCounter()
|
||||
{
|
||||
counters_.reserve(5); // pre-allocated 5 slots
|
||||
}
|
||||
|
||||
|
||||
template<class X>
|
||||
long
|
||||
int64_t
|
||||
get() const
|
||||
{
|
||||
Lock sync(this);
|
||||
return counters_[slot<X>()];
|
||||
return counters_[slot<X>()].load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
template<class X>
|
||||
long
|
||||
int64_t
|
||||
inc()
|
||||
{
|
||||
Lock sync(this);
|
||||
return ++counters_[slot<X>()];
|
||||
{ // yields the value seen previously
|
||||
return 1 + counters_[slot<X>()].fetch_add(+1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
template<class X>
|
||||
long
|
||||
int64_t
|
||||
dec()
|
||||
{
|
||||
Lock sync(this);
|
||||
return --counters_[slot<X>()];
|
||||
return -1 + counters_[slot<X>()].fetch_add(-1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -198,6 +202,11 @@ namespace lib {
|
|||
* Utility to produce member IDs
|
||||
* for objects belonging to a "Family",
|
||||
* as defined by a distinguishing type.
|
||||
* Within each family, each new instance of
|
||||
* FamilyMember holds a new distinct id number.
|
||||
* @remark this builds a structure similar to TypedContext,
|
||||
* however the second level is not assigned _per type_
|
||||
* but rather _per instance_ of FamilyMember<TY>
|
||||
*/
|
||||
template<typename TY>
|
||||
class FamilyMember
|
||||
|
|
@ -211,7 +220,7 @@ namespace lib {
|
|||
static size_t
|
||||
allocateNextMember()
|
||||
{
|
||||
return memberCounter.fetch_add(+1, std::memory_order_relaxed);
|
||||
return 1 + memberCounter.fetch_add(+1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ return: 0
|
|||
END
|
||||
|
||||
|
||||
TEST "Create 20 Threads passing context" ThreadWrapper_test <<END
|
||||
TEST "Run function concurrently" ThreadWrapper_test <<END
|
||||
return: 0
|
||||
END
|
||||
|
||||
|
|
@ -42,7 +42,7 @@ return: 0
|
|||
END
|
||||
|
||||
|
||||
TEST "Detect code runing in a given Thread" ThreadWrapperSelfRecognitionTest_test <<END
|
||||
TEST "Detect running in specific Thread" ThreadWrapperSelfRecognitionTest_test <<END
|
||||
return: 0
|
||||
END
|
||||
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ namespace test{
|
|||
} // (End) test setup....
|
||||
|
||||
using lib::ThreadJoinable;
|
||||
using error::LERR_(LOGIC);
|
||||
using std::rand;
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -42,268 +42,34 @@
|
|||
|
||||
|
||||
#include "lib/test/run.hpp"
|
||||
#include "lib/test/test-helper.hpp"
|
||||
#include "lib/typed-counter.hpp"
|
||||
#include "lib/scoped-ptrvect.hpp"
|
||||
#include "vault/thread-wrapper.hpp"
|
||||
#include "lib/util-foreach.hpp"
|
||||
#include "lib/sync.hpp"
|
||||
#include "lib/test/microbenchmark.hpp"
|
||||
#include "lib/util.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <cstdlib>
|
||||
#include <vector>
|
||||
#include <time.h>
|
||||
#include <utility>
|
||||
#include <array>
|
||||
|
||||
|
||||
namespace lib {
|
||||
namespace test{
|
||||
|
||||
|
||||
using vault::ThreadJoinable;
|
||||
using util::for_each;
|
||||
using util::isnil;
|
||||
using std::placeholders::_1;
|
||||
using std::bind;
|
||||
using std::ref;
|
||||
using std::vector;
|
||||
using std::rand;
|
||||
|
||||
|
||||
namespace { // test data and helpers...
|
||||
|
||||
const uint MAX_FAMILIES = 4; ///< maximum separate "families", each sharing a TypedCounter
|
||||
const uint MAX_MEMBERS = 10; ///< maximum members per family (member == test thread)
|
||||
const uint MAX_ITERATIONS = 50; ///< maximum iterations within a single test thread
|
||||
const uint MAX_DELAY_ms = 3; ///< maximum delay between check iterations
|
||||
|
||||
/* Hint: number of threads = MEMBERS * FAMILIES
|
||||
*
|
||||
* The values set here are fairly conservative,
|
||||
* but increasing the number of threads causes the test suite
|
||||
* to fail frequently. Please increase these values e.g.
|
||||
* to 20 and 50 for a more thorough stress test!
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Interface to a family of dummy types
|
||||
*/
|
||||
class DummyType
|
||||
{
|
||||
public:
|
||||
virtual ~DummyType() { }
|
||||
|
||||
/** core test operation: do a random increment or decrement
|
||||
* on the provided TypedCounter instance, and also save an
|
||||
* account to a local embedded checksum for verification */
|
||||
virtual void doCount (TypedCounter&) =0;
|
||||
|
||||
virtual void collect_externalCount (TypedCounter&) =0;
|
||||
virtual void collect_internalCount () =0;
|
||||
};
|
||||
|
||||
|
||||
/* === Checksums === */
|
||||
|
||||
long sum_TypedCounter_; ///< Sum1: calculated from TypedCounter
|
||||
long sum_internal_; ///< Sum2: control value calculated from Dummy::localChecksum_
|
||||
|
||||
void
|
||||
accountExternal (DummyType& target, TypedCounter& counter_to_use)
|
||||
{
|
||||
target.collect_externalCount (counter_to_use);
|
||||
}
|
||||
void
|
||||
accountInternal (DummyType& target)
|
||||
{
|
||||
target.collect_internalCount();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* To actually drive the TypedCounter invocations, we need a family
|
||||
* of different (but of course related) types. Actually, we use these
|
||||
* subclasses here also to carry out the invocations and the accounting
|
||||
* to build up the checksums for verification.
|
||||
*/
|
||||
template<uint i>
|
||||
class Dummy
|
||||
: public DummyType
|
||||
, public lib::Sync<>
|
||||
{
|
||||
long localChecksum_;
|
||||
|
||||
void
|
||||
record_internal (int increment)
|
||||
{
|
||||
Lock protect(this);
|
||||
localChecksum_ += increment;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
doCount (TypedCounter& counter)
|
||||
{
|
||||
// note: deliberately *not* synchronised
|
||||
|
||||
if (rand() % 2)
|
||||
{
|
||||
counter.inc<Dummy>();
|
||||
record_internal (+1);
|
||||
}
|
||||
else
|
||||
{
|
||||
counter.dec<Dummy>();
|
||||
record_internal (-1);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
collect_externalCount (TypedCounter& counter)
|
||||
{
|
||||
// Lock not necessary, because of invocation sequence
|
||||
sum_TypedCounter_ += counter.get<Dummy>();
|
||||
}
|
||||
|
||||
void
|
||||
collect_internalCount ()
|
||||
{
|
||||
sum_internal_ += localChecksum_;
|
||||
}
|
||||
|
||||
public:
|
||||
Dummy() : localChecksum_(0) {}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Collection of target functions,
|
||||
* to be invoked during the test run
|
||||
*/
|
||||
struct DummyTarget
|
||||
{
|
||||
typedef ScopedPtrVect<DummyType> TargetVect;
|
||||
|
||||
TargetVect targets_;
|
||||
|
||||
DummyTarget ()
|
||||
: targets_(10)
|
||||
{
|
||||
targets_.manage(new Dummy<0>);
|
||||
targets_.manage(new Dummy<1>);
|
||||
targets_.manage(new Dummy<2>);
|
||||
targets_.manage(new Dummy<3>);
|
||||
targets_.manage(new Dummy<4>);
|
||||
targets_.manage(new Dummy<5>);
|
||||
targets_.manage(new Dummy<6>);
|
||||
targets_.manage(new Dummy<7>);
|
||||
targets_.manage(new Dummy<8>);
|
||||
targets_.manage(new Dummy<9>);
|
||||
}
|
||||
|
||||
|
||||
/** entry point for the SingleCheck instances
|
||||
* to trigger off a single invocation
|
||||
*/
|
||||
void
|
||||
torture (TypedCounter& counter_to_use)
|
||||
{
|
||||
uint victim = (rand() % 10);
|
||||
targets_[victim].doCount (counter_to_use);
|
||||
}
|
||||
|
||||
|
||||
typedef TargetVect::iterator iterator;
|
||||
|
||||
/** allow Iteration over all targets in the TargetVect */
|
||||
iterator begin() { return targets_.begin(); }
|
||||
iterator end() { return targets_.end(); }
|
||||
};
|
||||
|
||||
DummyTarget targetCollection;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Each single check runs in a separate thread
|
||||
* and performs a random sequence of increments
|
||||
* and decrements on random targets.
|
||||
*/
|
||||
class SingleCheck
|
||||
: ThreadJoinable
|
||||
{
|
||||
public:
|
||||
SingleCheck (TypedCounter& counter_to_use)
|
||||
: ThreadJoinable("TypedCounter_test worker Thread"
|
||||
, bind (&SingleCheck::runCheckSequence, this, ref(counter_to_use), (rand() % MAX_ITERATIONS))
|
||||
)
|
||||
{ }
|
||||
|
||||
~SingleCheck () { this->join(); }
|
||||
|
||||
|
||||
private:
|
||||
void runCheckSequence(TypedCounter& counter, uint iterations)
|
||||
{
|
||||
do
|
||||
{
|
||||
usleep (1000 * (rand() % MAX_DELAY_ms));
|
||||
targetCollection.torture (counter);
|
||||
}
|
||||
while (iterations--);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Family of individual checks, sharing
|
||||
* a common TypedCounter instance.
|
||||
*/
|
||||
struct TestFamily
|
||||
{
|
||||
TypedCounter ourCounter_;
|
||||
ScopedPtrVect<SingleCheck> checks_;
|
||||
|
||||
TestFamily()
|
||||
: checks_(MAX_MEMBERS)
|
||||
{
|
||||
uint members (1 + rand() % MAX_MEMBERS);
|
||||
while (members--)
|
||||
checks_.manage (new SingleCheck (ourCounter_));
|
||||
}
|
||||
|
||||
~TestFamily()
|
||||
{
|
||||
checks_.clear(); // blocks until all test threads finished
|
||||
account();
|
||||
}
|
||||
|
||||
void
|
||||
account()
|
||||
{
|
||||
for_each ( targetCollection
|
||||
, bind (accountExternal, _1, ourCounter_)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** a series of independent context sets */
|
||||
typedef ScopedPtrVect<TestFamily> FamilyTable;
|
||||
|
||||
namespace { // test parametrisation...
|
||||
const uint MAX_INDEX = 10; ///< number of distinct types / counters
|
||||
const uint NUM_THREADS = 100; ///< number of threads to run in parallel
|
||||
const uint NUM_ITERATIONS = 10000; ///< number of repeated random accesses per Thread
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***********************************************************************************//**
|
||||
* @test build multiple sets of type-based contexts and run a simple counting operation
|
||||
* in each of them concurrently. Check the proper allocation of type-IDs in each
|
||||
* context and verify correct counting operation by checksum.
|
||||
*
|
||||
* @test verify the TypedCounter, which allows to maintain a counter-per-type.
|
||||
* - demonstrate behaviour
|
||||
* - concurrent test
|
||||
* @see TypedAllocationManager
|
||||
* @see typed-counter.hpp
|
||||
*/
|
||||
|
|
@ -319,7 +85,7 @@ namespace test{
|
|||
|
||||
|
||||
void
|
||||
simpleUsageTest ()
|
||||
simpleUsageTest()
|
||||
{
|
||||
TypedCounter myCounter;
|
||||
CHECK (isnil (myCounter));
|
||||
|
|
@ -365,23 +131,69 @@ namespace test{
|
|||
}
|
||||
|
||||
|
||||
|
||||
/** parametrised marker type to designate a counter to be incremented */
|
||||
template<size_t i>
|
||||
struct Dummy { };
|
||||
|
||||
template<size_t i>
|
||||
static void
|
||||
increment (TypedCounter& counter)
|
||||
{
|
||||
counter.inc<Dummy<i>>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for #tortureTest():
|
||||
* Build a table of functors, where the i-th entry invokes the function
|
||||
* increment<i>(), which leads to incrementing the counter for Dummy<i>.
|
||||
*/
|
||||
template<size_t...I>
|
||||
static auto
|
||||
buildOperatorsTable(std::index_sequence<I...>)
|
||||
{
|
||||
using Operator = void(*)(TypedCounter&);
|
||||
return std::array<Operator, MAX_INDEX>{increment<I>...};
|
||||
}
|
||||
|
||||
template<size_t...I>
|
||||
static size_t
|
||||
sumAllCounters(TypedCounter& counter, std::index_sequence<I...>)
|
||||
{
|
||||
return (counter.get<Dummy<I>>() + ... );
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @test verify TypedCounter concurrency safety
|
||||
* - use a set of types `Dummy<i>` to access a corresponding counter
|
||||
* - run a large number of threads in parallel, each incrementing
|
||||
* a randomly picked counter; this is achieved by using a table
|
||||
* of »increment operators«, where each one is tied to a specific
|
||||
* Dummy<i>.
|
||||
*/
|
||||
void
|
||||
tortureTest ()
|
||||
tortureTest()
|
||||
{
|
||||
std::srand (::time (NULL));
|
||||
sum_TypedCounter_ = 0;
|
||||
sum_internal_ = 0;
|
||||
|
||||
uint num_Families (1 + rand() % MAX_FAMILIES);
|
||||
using IDX = std::make_index_sequence<MAX_INDEX>;
|
||||
auto operators = buildOperatorsTable(IDX{});
|
||||
|
||||
FamilyTable testFamilies(num_Families);
|
||||
for (uint i=0; i<num_Families; ++i)
|
||||
testFamilies.manage (new TestFamily);
|
||||
TypedCounter testCounter;
|
||||
|
||||
testFamilies.clear(); // blocks until all threads have terminated
|
||||
auto testSubject = [&](size_t) -> size_t
|
||||
{
|
||||
uint i = rand() % MAX_INDEX;
|
||||
operators[i](testCounter);
|
||||
return 1;
|
||||
};
|
||||
|
||||
for_each (targetCollection, accountInternal);
|
||||
CHECK (sum_TypedCounter_ == sum_internal_);
|
||||
threadBenchmark<NUM_THREADS> (testSubject, NUM_ITERATIONS);
|
||||
|
||||
size_t expectedIncrements = NUM_THREADS * NUM_ITERATIONS;
|
||||
CHECK (sumAllCounters(testCounter, IDX{}) == expectedIncrements);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -80522,7 +80522,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200<br/>
|
|||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
</node>
|
||||
<node COLOR="#338800" CREATED="1695679685747" ID="ID_1841477437" MODIFIED="1696368998612" TEXT="DiagnosticContext_test">
|
||||
<node COLOR="#338800" CREATED="1695679685747" FOLDED="true" ID="ID_1841477437" MODIFIED="1696368998612" TEXT="DiagnosticContext_test">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node COLOR="#435e98" CREATED="1695681285804" ID="ID_478666570" MODIFIED="1696369059125" TEXT="Problem: Fehlerübergabe">
|
||||
<linktarget COLOR="#b65b7a" DESTINATION="ID_478666570" ENDARROW="Default" ENDINCLINATION="41;-72;" ID="Arrow_ID_1595086726" SOURCE="ID_1998022902" STARTARROW="None" STARTINCLINATION="-427;24;"/>
|
||||
|
|
@ -80538,8 +80538,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200<br/>
|
|||
</node>
|
||||
<node CREATED="1695681283396" ID="ID_1116798044" MODIFIED="1695681285000" TEXT="verify_heavilyParallelUsage"/>
|
||||
</node>
|
||||
<node COLOR="#338800" CREATED="1695679805882" FOLDED="true" ID="ID_62892662" MODIFIED="1696180242543" TEXT="SessionCommandFunction_test">
|
||||
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1695679805882" ID="ID_62892662" MODIFIED="1696451002137" TEXT="SessionCommandFunction_test">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<icon BUILTIN="flag-pink"/>
|
||||
<node COLOR="#5b280f" CREATED="1695681374017" ID="ID_767210660" MODIFIED="1696369156711" TEXT="man könnte/sollte Fehler explizit fangen — als Konsitenzcheck">
|
||||
<icon BUILTIN="button_cancel"/>
|
||||
<node CREATED="1696369157990" ID="ID_1718564394" MODIFIED="1696369162654" TEXT="besser: maybeThrow()"/>
|
||||
|
|
@ -80886,6 +80887,19 @@ Date:   Thu Apr 20 18:53:17 2023 +0200<br/>
|
|||
<node COLOR="#338800" CREATED="1696179937254" ID="ID_1888898055" MODIFIED="1696179947701" TEXT="damit läuft der Test wieder (endlich)">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1696451004673" ID="ID_1297218008" MODIFIED="1696451035537" TEXT="Regression">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
CHECK: session-command-function-test.cpp:425: thread_1: perform_massivelyParallel: (testCommandState - prevState == Time(expectedOffset))
|
||||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
<icon BUILTIN="broken-line"/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1695394237210" ID="ID_11553358" MODIFIED="1696029630733" TEXT="Applikation umstellen">
|
||||
|
|
@ -80898,7 +80912,170 @@ Date:   Thu Apr 20 18:53:17 2023 +0200<br/>
|
|||
<node COLOR="#338800" CREATED="1696030280182" ID="ID_244374373" MODIFIED="1696366309566" TEXT="SyncBarrier_test + Microbenchmark">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1696029465124" ID="ID_1556068052" MODIFIED="1696029514685" TEXT="typed-counter-test.cpp (/zLumi/tests/basics)"/>
|
||||
<node COLOR="#338800" CREATED="1696029465124" FOLDED="true" ID="ID_1556068052" MODIFIED="1696448961733" TEXT="TypedCounter_test">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1696373983769" ID="ID_878886847" MODIFIED="1696448926058" TEXT="mein Eindruck: dieser Test will besonders „wild“ sein">
|
||||
<icon BUILTIN="yes"/>
|
||||
<node CREATED="1696448935710" ID="ID_978675521" MODIFIED="1696448943961" TEXT="ist aber kaum zu durchschauen"/>
|
||||
<node CREATED="1696448944504" ID="ID_478816398" MODIFIED="1696448953899" TEXT="und testet nicht systematisch">
|
||||
<icon BUILTIN="messagebox_warning"/>
|
||||
</node>
|
||||
</node>
|
||||
<node COLOR="#435e98" CREATED="1696374018070" ID="ID_1519032076" MODIFIED="1696448914411">
|
||||
<richcontent TYPE="NODE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
würde es nicht genügen,
|
||||
</p>
|
||||
<p>
|
||||
einen einzigen Kontext concurrent zu testen?
|
||||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
<icon BUILTIN="help"/>
|
||||
<node COLOR="#435e98" CREATED="1696380070324" ID="ID_136993666" MODIFIED="1696380453937" TEXT="welche contentions müssen getestet werden?">
|
||||
<icon BUILTIN="help"/>
|
||||
<node CREATED="1696380210842" ID="ID_770181581" MODIFIED="1696430126072" TEXT="counter.inc<TY>()"/>
|
||||
<node CREATED="1696380250356" ID="ID_173251431" MODIFIED="1696443282415" TEXT="slot<X>() ⟻ wird aber bisher gar nicht abgedeckt">
|
||||
<arrowlink COLOR="#f81c54" DESTINATION="ID_1155198254" ENDARROW="Default" ENDINCLINATION="825;-52;" ID="Arrow_ID_44123997" STARTARROW="None" STARTINCLINATION="298;16;"/>
|
||||
<icon BUILTIN="broken-line"/>
|
||||
</node>
|
||||
</node>
|
||||
<node COLOR="#435e98" CREATED="1696380444194" ID="ID_963223127" MODIFIED="1696448919381" TEXT="das ist doch alles gar nicht so wild!">
|
||||
<icon BUILTIN="idea"/>
|
||||
<node CREATED="1696430390731" ID="ID_348950521" MODIFIED="1696430404173" TEXT="aggressive Zufalls-Zugriffe genügen"/>
|
||||
<node CREATED="1696430512599" ID="ID_1435359661" MODIFIED="1696430683355" TEXT="nicht-deterministische Reihenfolge der Zugriffe">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
...das bedeutet, es wird erst zur Laufzeit bestimmt, in welcher Reihenfolge die Counter für die Typen alloziert werden; damit kommt es zu Beginn zu einer aggressiven contention auf slot<X>. <u>Einschränkung</u>: in dieser Form wirkt dieser Test nur beim ersten Lauf innerhalb einer Programm-Instanz, weil danach die Slots belegt sind. <i>In der Praxis stellt das keine Einschränkung dar</i>
|
||||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
</node>
|
||||
<node CREATED="1696430836095" ID="ID_1146635545" MODIFIED="1696431016213" TEXT="Contention auf counter selber ist etwas schwieriger">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
...denn es muß eine hohe Wahrscheinlichkeit geben, daß gleichzeitig zwei Threads auf den gleichen Counter zugreifen ⟹ es muß deutlich mehr Threads geben als counter. Das ist aber schwierig, weil die Zahl der Cores beschränkt ist. Hier hilft nur (a) sehr viel zu viele Threads verwenden und (b) diese lang laufen lassen.
|
||||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
<node CREATED="1696431018358" ID="ID_721191123" MODIFIED="1696431023649" TEXT="10 counter"/>
|
||||
<node CREATED="1696431024238" ID="ID_1773086920" MODIFIED="1696431027272" TEXT="100 Threads"/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1696374076405" ID="ID_1206073156" MODIFIED="1696374093126" TEXT="das könnte man dann direkt mit dem threadedMicrobenchmark tun">
|
||||
<icon BUILTIN="idea"/>
|
||||
</node>
|
||||
<node COLOR="#338800" CREATED="1696431038235" ID="ID_540054086" MODIFIED="1696448417094" TEXT="komplett neue Implementierung">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node CREATED="1696431259538" ID="ID_1860790357" MODIFIED="1696431287600" TEXT="Typ mit Index-Template-Parameter: Dummy<i>"/>
|
||||
<node CREATED="1696432169621" ID="ID_1235729075" MODIFIED="1696432189438" TEXT="dazu parametrisierte Zugriffs-Operatoren"/>
|
||||
<node CREATED="1696437474632" ID="ID_583782137" MODIFIED="1696437653078" TEXT="ad-hoc polimorphismus nutzen">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
das heißt ich muß keinen abstrakten Typ mehr konstruieren und daraus abgeleitete Dummy<i>, weil letzten Endes nur zwei Operationen notwendig sind: den Zähler inkrementieren und am Ende den Zählerstand auslesen
|
||||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
<node CREATED="1696437495125" ID="ID_1620361815" MODIFIED="1696437503616" TEXT="parametrisierte Funktion"/>
|
||||
<node CREATED="1696437504604" ID="ID_745569323" MODIFIED="1696437545888" TEXT="std::index_sequence und variadic paremeter packs"/>
|
||||
</node>
|
||||
<node COLOR="#435e98" CREATED="1696437656351" ID="ID_1663233452" MODIFIED="1696448435697" TEXT="nur einmal inkrementieren pro Testfall ⟹ Prüfsumme muß Zahl der Iterationen sein">
|
||||
<icon BUILTIN="idea"/>
|
||||
</node>
|
||||
<node COLOR="#338800" CREATED="1696438031400" ID="ID_1665607683" MODIFIED="1696448432366" TEXT="insgesamt wird der Testaufbau dadurch drastich knapper">
|
||||
<icon BUILTIN="idea"/>
|
||||
<node CREATED="1696438065305" ID="ID_1118255590" MODIFIED="1696438076767" TEXT="ob er klarer ist.. ich hoffe so">
|
||||
<icon BUILTIN="smiley-oh"/>
|
||||
</node>
|
||||
<node COLOR="#338800" CREATED="1696438081968" ID="ID_1642767511" MODIFIED="1696438177738" TEXT="korrekte Funktionsweise durch pre-Tests verifiziert">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node CREATED="1696438096067" ID="ID_1420275370" MODIFIED="1696438120724" TEXT="die Operatoren inkrementieren tatsächlich den ihnen korrespondierenden Counter"/>
|
||||
<node CREATED="1696438121553" ID="ID_1994306616" MODIFIED="1696438145938" TEXT="testSubject()-Aufrufe inkrementieren zufällige Counter"/>
|
||||
<node CREATED="1696438146526" ID="ID_1082515240" MODIFIED="1696438175732" TEXT="die Belegung der Type-IDs erfolgt tatsächlich bei erstem Gebrauch">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
das gilt aber nur, wenn nicht bereits summAllCounters() aufgerufen wurde!
|
||||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node COLOR="#338800" CREATED="1696443226741" ID="ID_22926820" MODIFIED="1696448412591" TEXT="Test">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1696443235313" ID="ID_1155198254" MODIFIED="1696448784114" TEXT="Tja... ein Bug in der concurrent Slot-Belegung">
|
||||
<linktarget COLOR="#f81c54" DESTINATION="ID_1155198254" ENDARROW="Default" ENDINCLINATION="825;-52;" ID="Arrow_ID_44123997" SOURCE="ID_173251431" STARTARROW="None" STARTINCLINATION="298;16;"/>
|
||||
<icon BUILTIN="broken-line"/>
|
||||
<node CREATED="1696443290736" HGAP="36" ID="ID_413296311" MODIFIED="1696448896455" TEXT="war wohl geblendet von der Synchronisation der Typ-IDs" VSHIFT="38"/>
|
||||
<node CREATED="1696443313884" ID="ID_1612183462" MODIFIED="1696448803529" TEXT="hab einen Race übersehen beim Vergrößern der Storage">
|
||||
<icon BUILTIN="clanbomber"/>
|
||||
<node CREATED="1696443353331" ID="ID_429189452" MODIFIED="1696443363674" TEXT="die POST-Assertion hat's aufgedeckt"/>
|
||||
<node CREATED="1696443364166" ID="ID_1239565047" MODIFIED="1696443383863" TEXT="d.h. die Storage wurde inzwischen schon für einen anderen Thread vergrößert"/>
|
||||
<node CREATED="1696448812795" ID="ID_1174382727" MODIFIED="1696448869795" TEXT="könnte die Datenstruktur korrumpieren oder bereits allozierte counter wieder verwerfen"/>
|
||||
<node CREATED="1696448870455" ID="ID_511794109" MODIFIED="1696448877378" TEXT=" ⟹ out-of-bounds access"/>
|
||||
</node>
|
||||
<node COLOR="#338800" CREATED="1696443386579" ID="ID_1397943659" MODIFIED="1696448504750" TEXT="Lösung: double-checked Locking verwenden">
|
||||
<linktarget COLOR="#a9b4c1" DESTINATION="ID_1397943659" ENDARROW="Default" ENDINCLINATION="198;12;" ID="Arrow_ID_1393329601" SOURCE="ID_971300" STARTARROW="None" STARTINCLINATION="231;0;"/>
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1696443412807" ID="ID_1465828744" MODIFIED="1696448782099" TEXT="ein weiterer Fehler in den genfunc/generateSymbolicID">
|
||||
<icon BUILTIN="broken-line"/>
|
||||
<node CREATED="1696443435736" ID="ID_410980608" MODIFIED="1696443440463" TEXT="da war ich wohl zu faul..."/>
|
||||
<node CREATED="1696443440963" ID="ID_138836655" MODIFIED="1696443452778" TEXT="übersehen daß die static instance pro Funktion ist"/>
|
||||
<node CREATED="1696443453282" ID="ID_268210175" MODIFIED="1696443461533" TEXT="und diese Funktion hier ist getemplated!"/>
|
||||
<node COLOR="#338800" CREATED="1696443462884" ID="ID_1431962479" MODIFIED="1696448402814" TEXT="Lösung: per Meyers Singleton auf einen gemeinsamen TypedCounter zugreifen">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node COLOR="#338800" CREATED="1696448462919" ID="ID_282777557" MODIFIED="1696448471819" TEXT="TypedCounter umstellen auf Atomics">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node CREATED="1696448478627" ID="ID_427546373" MODIFIED="1696448485598" TEXT="geht nur für die Counter selber"/>
|
||||
<node CREATED="1696448486162" ID="ID_971300" MODIFIED="1696448508466" TEXT="die Datenstruktur muß per Lock geschützt werden">
|
||||
<arrowlink DESTINATION="ID_1397943659" ENDARROW="Default" ENDINCLINATION="198;12;" ID="Arrow_ID_1393329601" STARTARROW="None" STARTINCLINATION="231;0;"/>
|
||||
<icon BUILTIN="messagebox_warning"/>
|
||||
</node>
|
||||
<node COLOR="#435e98" CREATED="1696446079844" ID="ID_1523681465" MODIFIED="1696448773836" TEXT="Benchmark">
|
||||
<icon BUILTIN="list"/>
|
||||
<node CREATED="1696448546634" ID="ID_1319006212" MODIFIED="1696448562108" TEXT="nur marginale Unterschiede feststellbar">
|
||||
<node CREATED="1696448728818" ID="ID_294519711" MODIFIED="1696448740972" TEXT="der Mutex ist hier non-recursive"/>
|
||||
<node CREATED="1696448741800" ID="ID_1141820497" MODIFIED="1696448760640" TEXT="Vermutung: es dominiert der Datenstruktur-Zugriff"/>
|
||||
</node>
|
||||
<node CREATED="1696448686944" ID="ID_1450093366" MODIFIED="1696448690980" TEXT="Messungen mit -O3"/>
|
||||
<node CREATED="1696448564361" ID="ID_1608888848" MODIFIED="1696448582009" TEXT="im multithread-Setup dominiert die Wartezeit">
|
||||
<node CREATED="1696448586951" ID="ID_797208087" MODIFIED="1696448604142" TEXT="100 Threads ≈ 50µs"/>
|
||||
<node CREATED="1696448606458" ID="ID_1705996862" MODIFIED="1696448652330" TEXT="8 Threads ≈ 3.8µs (Lock) / 3.1µs (Atomic)"/>
|
||||
</node>
|
||||
<node CREATED="1696448657779" ID="ID_1394995329" MODIFIED="1696448672325" TEXT="single-threaded: beide ≈ 60ns"/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1696450881793" ID="ID_1438190212" MODIFIED="1696450893071" TEXT="TypedFamilyMemberID_test">
|
||||
<icon BUILTIN="flag-yellow"/>
|
||||
</node>
|
||||
<node CREATED="1696029465120" ID="ID_1705540772" MODIFIED="1696029510893" TEXT="call-queue-test.cpp (/zLumi/tests/basics)"/>
|
||||
<node CREATED="1696029465120" ID="ID_1782746519" MODIFIED="1696029465120" TEXT="bus-term-test.cpp (/zLumi/tests/stage)"/>
|
||||
</node>
|
||||
|
|
|
|||
Loading…
Reference in a new issue