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:
Fischlurch 2023-10-04 22:41:00 +02:00
parent 80f09cb33b
commit 4f50cbc386
7 changed files with 299 additions and 293 deletions

View file

@ -62,5 +62,11 @@ namespace idi {
} //(End)integration helpers...
TypedCounter&
sharedInstanceCounter()
{ // Meyer's Singleton
static TypedCounter instanceCounter;
return instanceCounter;
}
}} // namespace lib::idi

View file

@ -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>());
}
/**

View file

@ -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,54 +138,55 @@ 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
slot() const
{
IxID typeID = TypedContext<TypedCounter>::ID<TY>::get();
if (size() < 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:

View file

@ -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

View file

@ -57,6 +57,7 @@ namespace test{
} // (End) test setup....
using lib::ThreadJoinable;
using error::LERR_(LOGIC);
using std::rand;

View file

@ -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);
}
};

View file

@ -80522,7 +80522,7 @@ Date:&#160;&#160;&#160;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&#xfc;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:&#160;&#160;&#160;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&#xf6;nnte/sollte Fehler explizit fangen &#x2014; als Konsitenzcheck">
<icon BUILTIN="button_cancel"/>
<node CREATED="1696369157990" ID="ID_1718564394" MODIFIED="1696369162654" TEXT="besser: maybeThrow()"/>
@ -80886,6 +80887,19 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<node COLOR="#338800" CREATED="1696179937254" ID="ID_1888898055" MODIFIED="1696179947701" TEXT="damit l&#xe4;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:&#160;&#160;&#160;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 &#x201e;wild&#x201c; 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&#252;rde es nicht gen&#252;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&#xfc;ssen getestet werden?">
<icon BUILTIN="help"/>
<node CREATED="1696380210842" ID="ID_770181581" MODIFIED="1696430126072" TEXT="counter.inc&lt;TY&gt;()"/>
<node CREATED="1696380250356" ID="ID_173251431" MODIFIED="1696443282415" TEXT="slot&lt;X&gt;() &#x27fb; 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&#xfc;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&#252;r die Typen alloziert werden; damit kommt es zu Beginn zu einer aggressiven contention auf slot&lt;X&gt;. <u>Einschr&#228;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&#228;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&#223; eine hohe Wahrscheinlichkeit geben, da&#223; gleichzeitig zwei Threads auf den gleichen Counter zugreifen &#10233; es mu&#223; deutlich mehr Threads geben als counter. Das ist aber schwierig, weil die Zahl der Cores beschr&#228;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&#xf6;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&lt;i&gt;"/>
<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&#223;t ich mu&#223; keinen abstrakten Typ mehr konstruieren und daraus abgeleitete Dummy&lt;i&gt;, weil letzten Endes nur zwei Operationen notwendig sind: den Z&#228;hler inkrementieren und am Ende den Z&#228;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 &#x27f9; Pr&#xfc;fsumme mu&#xdf; 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&#xe4;chlich den ihnen korrespondierenden Counter"/>
<node CREATED="1696438121553" ID="ID_1994306616" MODIFIED="1696438145938" TEXT="testSubject()-Aufrufe inkrementieren zuf&#xe4;llige Counter"/>
<node CREATED="1696438146526" ID="ID_1082515240" MODIFIED="1696438175732" TEXT="die Belegung der Type-IDs erfolgt tats&#xe4;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 &#xfc;bersehen beim Vergr&#xf6;&#xdf;ern der Storage">
<icon BUILTIN="clanbomber"/>
<node CREATED="1696443353331" ID="ID_429189452" MODIFIED="1696443363674" TEXT="die POST-Assertion hat&apos;s aufgedeckt"/>
<node CREATED="1696443364166" ID="ID_1239565047" MODIFIED="1696443383863" TEXT="d.h. die Storage wurde inzwischen schon f&#xfc;r einen anderen Thread vergr&#xf6;&#xdf;ert"/>
<node CREATED="1696448812795" ID="ID_1174382727" MODIFIED="1696448869795" TEXT="k&#xf6;nnte die Datenstruktur korrumpieren oder bereits allozierte counter wieder verwerfen"/>
<node CREATED="1696448870455" ID="ID_511794109" MODIFIED="1696448877378" TEXT=" &#x27f9; out-of-bounds access"/>
</node>
<node COLOR="#338800" CREATED="1696443386579" ID="ID_1397943659" MODIFIED="1696448504750" TEXT="L&#xf6;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="&#xfc;bersehen da&#xdf; 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&#xf6;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&#xfc;r die Counter selber"/>
<node CREATED="1696448486162" ID="ID_971300" MODIFIED="1696448508466" TEXT="die Datenstruktur mu&#xdf; per Lock gesch&#xfc;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 &#x2248; 50&#xb5;s"/>
<node CREATED="1696448606458" ID="ID_1705996862" MODIFIED="1696448652330" TEXT="8 Threads &#x2248; 3.8&#xb5;s (Lock) / 3.1&#xb5;s (Atomic)"/>
</node>
<node CREATED="1696448657779" ID="ID_1394995329" MODIFIED="1696448672325" TEXT="single-threaded: beide &#x2248; 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>