Library: improve initial population of ScopedCollection
This is a collection with unique-ownership, and the ability to create all content objects in ''RAII style'' from the constructor call, using a ''population functor''. It is often used to maintain a collection of resource handles, and especially to start a series of threads (for testing). With the variadic λ-captures from C++20, it is possible to rewrite the most frequently used population functors to be easier to use. Notably we can now bind constructor arguments, that will be passed to each of the created elements — a prominent example being the thread function when launching several threads. Furthermore, I added a constructor variant that consumes a Lumiera Iterator. Adding this kind of generic variants is possible now with C++20, since overload resolution takes concepts into account.
This commit is contained in:
parent
3a491855e7
commit
7448b81d49
8 changed files with 358 additions and 175 deletions
|
|
@ -88,6 +88,33 @@ test with random numbers::
|
|||
several threads; each thread should use its own generator then (yet this can be handled sloppy if
|
||||
the _quality_ of the random number actually does not matter!)
|
||||
|
||||
special test setup::
|
||||
It can be very helpful to use _policy based design_ for some complex evaluation or algorithm,
|
||||
even in cases where it is clear that the actual usage will only ever involve one specific data type
|
||||
and configuration. Not only does such a structured approach help with clarifying the design itself,
|
||||
since the core of the evaluation logic can be abstracted from data representation, but only the
|
||||
testing and verification of correctness is simplified, as the test can be written to use simple,
|
||||
human-readable data (strings, symbols), and, furthermore, many kinds of instrumentation can be
|
||||
included into the test data to gain detailed insight what is happening in the test subject.
|
||||
|
||||
verify behaviour sequence::
|
||||
Use the `lib::EventLog`:
|
||||
+
|
||||
- within the test, notable points are marked into the log, possibly with arguments
|
||||
- after the test, the Event Log can be verified with a search-sequence
|
||||
- this way it is possible to ensure that some events did happen, and happened
|
||||
in a very specific order, or that some event did _not_ happen after another event
|
||||
- the `test::Tracker` defined in 'src/test/tracking-dummy.hpp' will mark all constructor,
|
||||
destructor and copy events into an embedded Event Log instance. By inheriting or mix-in
|
||||
from that type, the allocation and instance behaviour of some service can be scrutinised.
|
||||
|
||||
verify incidences and concurrency::
|
||||
Aside from using some generic tracing utility, sometimes it is much easier to setup
|
||||
a _specifically tailored instrumentation_. This can be achieved with 'incidence-count.hpp'
|
||||
+
|
||||
- for instrumentation, the `markEnter()` and `markLeave()` functions need to be invoked
|
||||
- after the test run, various statistics can be computed and evaluated, including
|
||||
cumulated time and concurrency for the "cases" marked with enter / leave.
|
||||
|
||||
|
||||
Common Tasks
|
||||
|
|
@ -109,6 +136,12 @@ structured data::
|
|||
- also useful as output format, both for debugging and because it can be matched against
|
||||
an expected structure, which is generated with the DSL notation.
|
||||
- [yellow-background]#(planned)# will be able to map this from/to JSON easily.
|
||||
scoped collection::
|
||||
unique-ownership collection that holds a sequence of objects in a preallocated fixed
|
||||
storage block. Notably even non-copyable objects can be managed (which is not possible
|
||||
with a `std::vector`), and type-erased instances of subclasses can be mixed. Furthermore
|
||||
there is a constructor variant that allows to populate a `ScopedCollection` in RAII-style.
|
||||
This can e.g. be used to fire-off a set of Threads or open a set of wrapped connections.
|
||||
|
||||
Iterating
|
||||
~~~~~~~~~
|
||||
|
|
@ -134,6 +167,26 @@ IterSource::
|
|||
even the production-state can be hooked up into the IterSource instance (with a smart-ptr).
|
||||
This allows e.g. to send a Diff to the UI through the UI-Bus, while the actual generation
|
||||
and application both happen demand-driven or _lazy..._
|
||||
State Core::
|
||||
When some algorithm involves a statefull evaluation, it might be helpful to package that into
|
||||
a _State Core_ container. This is a class that is expected to expose three operations, which
|
||||
allow to build an iteration on top. The header 'iter-adapter.hpp' provides several adapters
|
||||
and wrappers, notable `lib::IterStateWrapper` wraps any _state core_ and provides a
|
||||
Lumiera Iterator interface; likewise, `IterExplorer` heavily relies on that concept and
|
||||
is able to accept and iterate any state core in a pipeline.
|
||||
special iterators::
|
||||
Some additional adaptors are provided as building block so that specific evaluations
|
||||
can be made to conform with the »Lumiera Iterator« concept
|
||||
+
|
||||
- several given iterators can be combined into a iteration over tuples with 'lib/iter-zip.hpp'
|
||||
- there is also a variation 'lib::izip' that adds a running index into the _zipping operation_
|
||||
- IterExplorer provides a `flatten()` operation, that can be used to join an iterator-of-iterators
|
||||
into a single seamless iteration lazily.
|
||||
- when a STL container also supports _backwards iteration_, then a lumiera iterator can be
|
||||
created that can _switch direction_ dynamically, with the help of 'iter-cursor.hpp'
|
||||
- a STL »deque« container can be used both as a stack and a queue; the header 'iter-stack.hpp'
|
||||
provides an iteration adapter for both usage schemes, so that either the stack or the queue
|
||||
can be _"discharged"_ as a Lumiera Iterator (possibly with an IterExplorer pipeline)
|
||||
|
||||
|
||||
Formatting
|
||||
|
|
@ -155,6 +208,25 @@ Formatting
|
|||
because there are way more capable solutions _out there_ ☺)
|
||||
|
||||
|
||||
Setup and wiring
|
||||
~~~~~~~~~~~~~~~~
|
||||
Generally speaking, we prefer _constructor wiring_ whenever possible, to avoid creating
|
||||
unnecessary statefulness (as would be the case when connecting dependencies through setters).
|
||||
This design also favours decomposition into self-contained components with a single purpose,
|
||||
and simplifies error handling with the help of the RAII pattern.
|
||||
|
||||
However, sometimes a piece of code depends on some remote and even global service; in these
|
||||
cases, the `lib::Depend` framework can be used to access that service via type name. The
|
||||
actual service can either be created on-demand as a singleton, or it can be installed
|
||||
explicitly as a service instance. Furthermore, it is possible to "push aside" the
|
||||
current service instance temporarily and replace it by a _test mock_.
|
||||
|
||||
One point to note is that `lib::Depend` is a _monostate_, and not a _singleton_.
|
||||
The actual service should be written as a free-standing class, that can be instantiated
|
||||
directly for the purpose of testing.
|
||||
|
||||
|
||||
|
||||
Language constructs
|
||||
-------------------
|
||||
Templates
|
||||
|
|
@ -240,7 +312,7 @@ index and iterate over tuple elements::
|
|||
This is another example of bridging the compile-time / runtime barrier, using a trampoline.
|
||||
Obviously such a solution requires that it is somehow possible to define a generic functor / λ that
|
||||
can accept any of the tuple elements. And, in order to build a runtime index access, we additionally
|
||||
need to convert each of the tuple elements into a _common reconciled type.
|
||||
need to convert each of the tuple elements into a _common reconciled type_.
|
||||
+
|
||||
- If such a type exists, can be figured out with the help of the `meta::CommonReturn` _metafunction_
|
||||
- `lib::meta::TupleIdxAdaptor` provides the necessary machinery to define a subscript `operator[]`
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
SCOPED-COLLECTION.hpp - managing a fixed collection of noncopyable polymorphic objects
|
||||
|
||||
Copyright (C)
|
||||
2012, Hermann Vosseler <Ichthyostega@web.de>
|
||||
2012,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
|
||||
|
|
@ -13,8 +13,8 @@
|
|||
|
||||
/** @file scoped-collection.hpp
|
||||
** Manage a collection of non-copyable polymorphic objects in compact storage.
|
||||
** This helper supports common situation where a service implementation has to
|
||||
** manages a fixed collection of implementation related sub-components, treated
|
||||
** This helper supports the common situation where a service implementation has
|
||||
** to manage a fixed collection of implementation related sub-components, treated
|
||||
** with reference semantics. Typically, those objects are used polymorphically,
|
||||
** and often even with _type erasure_ and added step by step through a builder.
|
||||
** The storage to hold all those child objects is allocated in one chunk
|
||||
|
|
@ -31,7 +31,7 @@
|
|||
** the managing scope is alive and able to access them.
|
||||
**
|
||||
** There are two different usage patterns for populating a ScopedCollection
|
||||
** - the "stack style" usage creates an empty container (using the one arg
|
||||
** - the **stack style** usage creates an empty container (using the one arg
|
||||
** ctor to specify the maximum size only). The storage capacity to hold
|
||||
** a number of objects, up to the predefined maximum, is (heap) allocated
|
||||
** immediately at construction of the container, yet no child objects are
|
||||
|
|
@ -39,11 +39,11 @@
|
|||
** by invoking #emplaceElement() to create a new element of the default
|
||||
** type `I`) or #emplace<Type>(args) to create some subtype. This way,
|
||||
** the container is gradually filled up.
|
||||
** - the "RAII style" usage strives to create all of the content objects
|
||||
** right away, immediately after the memory allocation. This usage pattern
|
||||
** avoids any kind of "lifecycle state". Either the container comes up sane
|
||||
** and fully populated, or the ctor call fails and any already created
|
||||
** objects are discarded.
|
||||
** - the **RAII-style** usage attempts to create all the contained objects
|
||||
** right away, immediately after memory allocation. This usage pattern
|
||||
** avoids any kind of _lifecycle state_. Either the container is in sane
|
||||
** state and fully populated from the outset, or the call to the ctor fails
|
||||
** and all objects that have already been created are discarded.
|
||||
** @note intentionally there is no operation to discard individual objects,
|
||||
** the only option is to #clear() the whole container.
|
||||
** @note the container can hold instances of a subclass of the type defined
|
||||
|
|
@ -69,7 +69,7 @@
|
|||
#include "lib/iter-adapter.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <type_traits>
|
||||
#include <concepts>
|
||||
|
||||
|
||||
namespace lib {
|
||||
|
|
@ -78,6 +78,7 @@ namespace lib {
|
|||
using LERR_(INDEX_BOUNDS);
|
||||
using LERR_(CAPACITY);
|
||||
|
||||
using meta::lum_iter;
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -100,8 +101,8 @@ namespace lib {
|
|||
public:
|
||||
/**
|
||||
* Storage Frame to hold one Child object.
|
||||
* The storage will be an heap allocated
|
||||
* array of such Wrapper objects.
|
||||
* The storage will be an heap allocated array
|
||||
* of such Wrapper objects.
|
||||
* @note doesn't manage the Child
|
||||
*/
|
||||
class ElementHolder
|
||||
|
|
@ -126,7 +127,6 @@ namespace lib {
|
|||
|
||||
|
||||
|
||||
|
||||
/** place object of type TY, forwarding ctor arguments */
|
||||
template<class TY, typename...ARGS>
|
||||
TY&
|
||||
|
|
@ -150,6 +150,9 @@ namespace lib {
|
|||
clear();
|
||||
}
|
||||
|
||||
/** create an empty ScopedCollection with given max capacity
|
||||
* @remark this ctor variant is intended for _stack-like_ usage
|
||||
*/
|
||||
explicit
|
||||
ScopedCollection (size_t maxElements)
|
||||
: level_(0)
|
||||
|
|
@ -164,7 +167,7 @@ namespace lib {
|
|||
* It gets an ElementHolder& as parameter, and should
|
||||
* use this to create an object of suitable I-subclass
|
||||
*/
|
||||
template<class CTOR>
|
||||
template<std::invocable<ElementHolder&> CTOR>
|
||||
ScopedCollection (size_t maxElements, CTOR builder)
|
||||
: level_(0)
|
||||
, capacity_(maxElements)
|
||||
|
|
@ -190,22 +193,44 @@ namespace lib {
|
|||
populate_by (builder,instance);
|
||||
}
|
||||
|
||||
/** variation of RAII-style: "pull" elements from an iterator.
|
||||
* Each element will be created directly by invoking either the copy ctor,
|
||||
* or a suitable 1-argument ctor, depending on the type yielded by the iterator.
|
||||
* No further elements will be created however, once the iterator is exhausted.
|
||||
* @note anything in accordance to the Lumiera Forward concept is acceptable.
|
||||
* This rules out just passing a plain STL iterator (because these can't
|
||||
* tell for themselves when they're exhausted). Use an suitable iter-adapter
|
||||
* instead, e.g. by invoking lib::iter_stl::eachElm(stl_container)
|
||||
*/
|
||||
template<lum_iter IT>
|
||||
ScopedCollection (size_t maxElements, IT&& iter)
|
||||
: level_(0)
|
||||
, capacity_(maxElements)
|
||||
, elements_(new ElementHolder[maxElements])
|
||||
{
|
||||
pull_from (std::forward<IT> (iter));
|
||||
}
|
||||
|
||||
/* == some pre-defined Builders == */
|
||||
|
||||
class FillAll; ///< fills the ScopedCollection with default constructed I-instances
|
||||
/** Builder: ills the given storage slot with a I-instance
|
||||
* constructed from copies of the given arguments.
|
||||
* @return builder-λ to pass into ScopeCollection's ctor. */
|
||||
template<typename...ARGS>
|
||||
static auto fill (ARGS&&... args);
|
||||
|
||||
template<typename TY>
|
||||
class FillWith; ///< fills the ScopedCollection with default constructed TY-instances
|
||||
/** Builder: fills the given storage slot with a TY-instance
|
||||
* constructed from copies of the given arguments. */
|
||||
template<typename TY, typename...ARGS>
|
||||
static auto fillWith (ARGS&&... args);
|
||||
|
||||
template<typename IT>
|
||||
class PullFrom; ///< fills by copy-constructing values pulled from the iterator IT
|
||||
/** Builder: fills the given storage slot from invoking the
|
||||
* provided factory functor, with either an I-element or a
|
||||
* I-subtype, depending on the functor's result type.
|
||||
* @note the functor will be copied in the resulting λ */
|
||||
template<std::invocable<> FUN>
|
||||
static auto invoke (FUN&& factory);
|
||||
|
||||
template<typename IT>
|
||||
static PullFrom<IT>
|
||||
pull (IT iter) ///< convenience shortcut to pull from any given Lumiera Forward Iterator
|
||||
{
|
||||
return PullFrom<IT> (iter);
|
||||
}
|
||||
|
||||
void
|
||||
clear()
|
||||
|
|
@ -247,7 +272,7 @@ namespace lib {
|
|||
* ElementHolder#create function, which places a new element
|
||||
* into the storage frame passed as parameter.
|
||||
*/
|
||||
template<class CTOR>
|
||||
template<std::invocable<ElementHolder&> CTOR>
|
||||
void
|
||||
populate_by (CTOR builder)
|
||||
try {
|
||||
|
|
@ -282,11 +307,46 @@ namespace lib {
|
|||
catch(...)
|
||||
{
|
||||
WARN (progress, "Failure while populating ScopedCollection. "
|
||||
"All elements will be discarded");
|
||||
"All elements will be discarded.");
|
||||
clear();
|
||||
throw;
|
||||
}
|
||||
|
||||
template<lum_iter IT>
|
||||
struct _BuildFromIter ///< @internal helper to determine what to build from an iterator
|
||||
{
|
||||
using Yield = iter::Yield<IT>;
|
||||
using Value = meta::ValueTypeBinding<IT>::value_type;
|
||||
using Type = std::conditional_t<meta::is_Subclass_v<Value, I>, Value, I>;
|
||||
static_assert (std::is_constructible_v<Type, Yield>
|
||||
,"Iterator must produce something "
|
||||
"that can be used to build contents of this container");
|
||||
};
|
||||
|
||||
/** variation of element initialisation,
|
||||
* creating further elements from the results of an iterator,
|
||||
* until either the container is filled up or the iterator exhausted.
|
||||
*/
|
||||
template<lum_iter IT>
|
||||
void
|
||||
pull_from (IT iter)
|
||||
try {
|
||||
while (iter and level_ < capacity_)
|
||||
{
|
||||
using Element = _BuildFromIter<IT>::Type;
|
||||
ElementHolder& storageFrame (elements_[level_]);
|
||||
storageFrame.template create<Element> (*iter);
|
||||
++level_;
|
||||
++iter;
|
||||
} }
|
||||
catch(...)
|
||||
{
|
||||
WARN (progress, "Failure while pulling Iterator into ScopedCollection. "
|
||||
"All elements will be discarded.");
|
||||
clear();
|
||||
throw;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** push a new element of default type
|
||||
|
|
@ -340,8 +400,8 @@ namespace lib {
|
|||
|
||||
|
||||
size_t size () const { return level_; }
|
||||
size_t capacity () const { return capacity_; }
|
||||
bool empty () const { return 0 == level_; }
|
||||
size_t capacity() const { return capacity_; }
|
||||
bool empty() const { return 0 == level_; }
|
||||
|
||||
|
||||
// use in standard range for loops...
|
||||
|
|
@ -354,7 +414,7 @@ namespace lib {
|
|||
private:
|
||||
/* ==== Storage: heap allocated array of element buffers ==== */
|
||||
|
||||
typedef std::unique_ptr<ElementHolder[]> ElementStorage;
|
||||
using ElementStorage = std::unique_ptr<ElementHolder[]>;
|
||||
|
||||
size_t level_;
|
||||
size_t capacity_;
|
||||
|
|
@ -417,66 +477,66 @@ namespace lib {
|
|||
/* === Supplement: pre-defined element builders === */
|
||||
|
||||
/** \par usage
|
||||
* Pass an instance of this builder functor as 2nd parameter
|
||||
* to ScopedCollections's ctor. (an anonymous instance is OK).
|
||||
* On invocation, this function generates a builder-λ to pass as
|
||||
* the 2nd parameter to ScopedCollections's ctor. When arguments are given,
|
||||
* these are perfect-forwarded into a copy in the closure, which implies that
|
||||
* all instances will receive either a copy or const-reference to these values.
|
||||
* (It is not possible to pass references this way).
|
||||
* Using this variant of the ctor switches the collection to RAII-style:
|
||||
* It will immediately try to create all the embedded objects, invoking this
|
||||
* It will immediately try to create all the embedded objects, invoking the
|
||||
* builder functor for each "slot" to hold such an embedded object. Actually,
|
||||
* this "slot" is an ElementHolder instance, which provides functions for
|
||||
* placement-creating objects into this embedded buffer.
|
||||
* @note allows also to default-create or copy-create
|
||||
*/
|
||||
template<class I, size_t siz>
|
||||
class ScopedCollection<I,siz>::FillAll
|
||||
{
|
||||
public:
|
||||
void
|
||||
operator() (ScopedCollection<I,siz>::ElementHolder& storage)
|
||||
{
|
||||
storage.template create<I>();
|
||||
}
|
||||
};
|
||||
template<typename...ARGS>
|
||||
auto
|
||||
ScopedCollection<I,siz>::fill (ARGS&&... args)
|
||||
{
|
||||
return [... args = std::forward<ARGS> (args)]
|
||||
(ScopedCollection<I,siz>::ElementHolder& storage)
|
||||
{
|
||||
storage.template create<I> (args...);
|
||||
};
|
||||
}
|
||||
|
||||
template<class I, size_t siz>
|
||||
template<typename TY>
|
||||
class ScopedCollection<I,siz>::FillWith
|
||||
{
|
||||
public:
|
||||
void
|
||||
operator() (ScopedCollection<I,siz>::ElementHolder& storage)
|
||||
{
|
||||
storage.template create<TY>();
|
||||
}
|
||||
};
|
||||
template<typename TY, typename...ARGS>
|
||||
auto
|
||||
ScopedCollection<I,siz>::fillWith (ARGS&&... args)
|
||||
{
|
||||
return [... args = std::forward<ARGS> (args)]
|
||||
(ScopedCollection<I,siz>::ElementHolder& storage)
|
||||
{
|
||||
storage.template create<TY> (args...);
|
||||
};
|
||||
}
|
||||
|
||||
/** \par usage
|
||||
* This variant allows to "pull" elements from an iterator.
|
||||
* Actually, the collection will try to create each element right away,
|
||||
* by invoking the copy ctor and passing the value yielded by the iterator.
|
||||
* @note anything in accordance to the Lumiera Forward Iterator pattern is OK.
|
||||
* This rules out just passing a plain STL iterator (because these can't
|
||||
* tell for themselves when they're exhausted). Use an suitable iter-adapter
|
||||
* instead, e.g. by invoking lib::iter_stl::eachElm(stl_container)
|
||||
/**
|
||||
* @remark this builder handles the technical aspect of creating elements
|
||||
* into the slots of the ElementHolder storage, so that client code
|
||||
* has only to define the constructor argument, which notably can
|
||||
* also be a subclass of \a I (assuming the storage size suffices)
|
||||
* @return builder-λ that can be passed into ScopeCollection's ctor.
|
||||
*/
|
||||
template<class I, size_t siz>
|
||||
template<typename IT>
|
||||
class ScopedCollection<I,siz>::PullFrom
|
||||
{
|
||||
IT iter_;
|
||||
|
||||
using ElementType = meta::ValueTypeBinding<IT>::value_type;
|
||||
|
||||
public:
|
||||
PullFrom (IT source)
|
||||
: iter_(source)
|
||||
{ }
|
||||
|
||||
void
|
||||
operator() (ScopedCollection<I,siz>::ElementHolder& storage)
|
||||
{
|
||||
storage.template create<ElementType> (*iter_);
|
||||
++iter_;
|
||||
}
|
||||
};
|
||||
template<std::invocable<> FUN>
|
||||
auto
|
||||
ScopedCollection<I,siz>::invoke (FUN&& factory)
|
||||
{
|
||||
using Res = std::invoke_result_t<FUN>;
|
||||
using Elem = std::conditional_t<meta::is_Subclass_v<Res, I>, Res, I>;
|
||||
static_assert (std::is_constructible_v<Elem, Res>
|
||||
,"Iterator must produce something "
|
||||
"that can be used to build contents of this container");
|
||||
|
||||
return [fac = std::forward<FUN> (factory)]
|
||||
(ScopedCollection<I,siz>::ElementHolder& storage) mutable
|
||||
{
|
||||
storage.template create<Elem> (fac());
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -451,6 +451,8 @@ namespace lib {
|
|||
* and possibly configure further details, depending on the actual Policy used.
|
||||
* @remark the primary ThreadLifecycle-ctor accepts such a Launch-instance
|
||||
* and invokes a chain of λ-functions collected in the member #launch
|
||||
* @note a Launch instance can be built by direct conversion from an arg-less
|
||||
* thread function (because the ctor is _not_ explicit)
|
||||
*/
|
||||
struct Launch
|
||||
: util::MoveOnly
|
||||
|
|
@ -571,6 +573,10 @@ namespace lib {
|
|||
* Primary constructor: Launch the new thread with flexible configuration.
|
||||
* @param launcher a #Launch builder with a λ-chain to configure and
|
||||
* finally trigger start of the thread
|
||||
* @note The Launch instance can be built by implicit conversion from any
|
||||
* arg-less invocable; this implies that ThreadLifecycle (and its
|
||||
* relevant subclasses) can be built without much ado directly
|
||||
* from a Lambda that defines the thread-function.
|
||||
*/
|
||||
ThreadLifecycle (Launch launcher)
|
||||
: Policy{launcher.id}
|
||||
|
|
@ -644,6 +650,12 @@ namespace lib {
|
|||
* thread has already copied the arguments and indeed actively started to run.
|
||||
* @warning The destructor waits for a short grace period of 20ms, but calls
|
||||
* `std::terminate` afterwards, should the thread still be active then.
|
||||
* @remark creating a Thread instance immediately starts the thread;
|
||||
* it can be built from
|
||||
* - any argument-less invocable or lambda (that is invoked in the thread)
|
||||
* - a string argument for the thread-id, an invocable, further arguments...
|
||||
* - a member pointer and further arguments; useful for derived classes
|
||||
* - a ThreadLifecycle::Launch, which allows fine grained configuration
|
||||
*/
|
||||
class Thread
|
||||
: public thread::ThreadLifecycle<thread::PolicyLaunchOnly>
|
||||
|
|
|
|||
|
|
@ -129,19 +129,19 @@ namespace test {
|
|||
|
||||
// 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());
|
||||
});
|
||||
using Threads = lib::ScopedCollection<Thread>;
|
||||
Threads threads{NUM_THREADS
|
||||
,Threads::fill ([&]{
|
||||
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
|
||||
|
|
|
|||
|
|
@ -379,9 +379,9 @@ namespace test{
|
|||
{
|
||||
CHECK (0 == Dummy::checksum());
|
||||
|
||||
CollD coll (25, CollD::FillAll() );
|
||||
CollD coll (25, CollD::fill() );
|
||||
|
||||
CHECK (!isnil (coll));
|
||||
CHECK (not isnil (coll));
|
||||
CHECK (25 == coll.size());
|
||||
CHECK (0 != Dummy::checksum());
|
||||
|
||||
|
|
@ -393,14 +393,16 @@ namespace test{
|
|||
}
|
||||
|
||||
|
||||
/** @test use a "populator" that creates a subclass and binds arguments */
|
||||
void
|
||||
verify_subclassPopulator()
|
||||
{
|
||||
CHECK (0 == Dummy::checksum());
|
||||
|
||||
CollD coll (25, CollD::FillWith<SubDummy>() );
|
||||
const uint arg{42};
|
||||
CollD coll (25, CollD::fillWith<SubDummy> (arg, 55) );
|
||||
|
||||
CHECK (!isnil (coll));
|
||||
CHECK (not isnil (coll));
|
||||
CHECK (25 == coll.size());
|
||||
CHECK (0 != Dummy::checksum());
|
||||
|
||||
|
|
@ -409,29 +411,32 @@ namespace test{
|
|||
}
|
||||
|
||||
|
||||
/** @test verify dynamic pulling of elements
|
||||
* - the first step uses a "populator" that invokes a λ
|
||||
* - the second step then uses the source container's iterator,
|
||||
* which happens to be a _Lumiera Forward Iterator_ to pull
|
||||
* copies into a second container.
|
||||
* - even while the second container is dimensioned to be larger,
|
||||
* only the number of elements yielded by the iterator are created.
|
||||
*/
|
||||
void
|
||||
verify_iteratorPopulator()
|
||||
{
|
||||
typedef ScopedCollection<uint> CollI;
|
||||
|
||||
CollI source (25);
|
||||
for (uint i=0; i < source.capacity(); ++i)
|
||||
source.emplace<uint>(i); // holding the numbers 0..24
|
||||
|
||||
CollI coll (20, CollI::pull(source.begin()));
|
||||
// this immediately pulls in the first 20 elements
|
||||
CHECK (!isnil (coll));
|
||||
CollI source (20, CollI::invoke([i=0] mutable { return i++; }));
|
||||
// filled with numbers 0..19
|
||||
CollI coll (30, source.begin()); // this immediately pulls in the first 20 elements
|
||||
CHECK (not isnil (coll));
|
||||
CHECK (20 == coll.size());
|
||||
CHECK (25 == source.size());
|
||||
CHECK (20 == source.size());
|
||||
CHECK (30 == coll.capacity());
|
||||
|
||||
for (uint i=0; i < coll.size(); ++i)
|
||||
{
|
||||
CHECK (coll[i] == i );
|
||||
CHECK (coll[i] == source[i]);
|
||||
}
|
||||
|
||||
// note: the iterator is assumed to deliver a sufficient amount of elements
|
||||
VERIFY_ERROR (ITER_EXHAUST, CollI (50, CollI::pull (source.begin())));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -19,10 +19,12 @@
|
|||
#include "test/run.hpp"
|
||||
#include "lib/sync-classlock.hpp"
|
||||
#include "lib/scoped-collection.hpp"
|
||||
#include "lib/iter-explorer.hpp"
|
||||
#include "lib/thread.hpp"
|
||||
|
||||
using test::Test;
|
||||
//using vault::ThreadJoinable; //////////////////WIP
|
||||
using lib::explore;
|
||||
using std::this_thread::yield;
|
||||
|
||||
namespace lib {
|
||||
namespace test {
|
||||
|
|
@ -58,29 +60,25 @@ namespace test {
|
|||
auto gen = buildCappedSubSequence(defaultGen);
|
||||
int contended = 0;
|
||||
|
||||
using Threads = lib::ScopedCollection<ThreadJoinable<>>;
|
||||
using Threads = lib::ScopedCollection<Thread>;
|
||||
|
||||
// Start a bunch of threads with random access pattern
|
||||
Threads threads{NUM_THREADS,
|
||||
[&](Threads::ElementHolder& storage)
|
||||
{
|
||||
storage.create<ThreadJoinable<>> ("Sync-ClassLock stress test"
|
||||
,[&]{
|
||||
for (uint i=0; i<NUM_LOOP; ++i)
|
||||
{
|
||||
uint delay = gen.i(10);
|
||||
usleep (delay);
|
||||
{
|
||||
ClassLock<void> guard;
|
||||
++contended;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
// Start a bunch of threads with random access pattern...
|
||||
Threads::fill ("Sync-ClassLock stress test"
|
||||
,[&]{
|
||||
for (uint i=0; i<NUM_LOOP; ++i)
|
||||
{
|
||||
uint delay = gen.i(10);
|
||||
usleep (delay);
|
||||
{
|
||||
ClassLock<void> guard;
|
||||
++contended;
|
||||
}
|
||||
}
|
||||
})};
|
||||
|
||||
for (auto& thread : threads)
|
||||
thread.join(); // block until thread terminates // @suppress("Return value not evaluated")
|
||||
while (explore(threads).has_any())
|
||||
yield(); // wait for all threads to terminate
|
||||
|
||||
CHECK (contended == NUM_THREADS * NUM_LOOP,
|
||||
"ALARM: Lock failed, concurrent modification "
|
||||
|
|
|
|||
|
|
@ -225,11 +225,9 @@ namespace test {
|
|||
CHECK (myPool.empty()); // no further pending / duplicate Alloc in-queue
|
||||
};
|
||||
|
||||
using Threads = lib::ScopedCollection<Thread>;
|
||||
auto startThread = [&](auto& storage){ storage.template create<Thread> (workSeq); };
|
||||
|
||||
// Start a collection of workers....
|
||||
Threads threads{NUM_THREADS, startThread};
|
||||
using Threads = lib::ScopedCollection<Thread>;
|
||||
Threads threads{NUM_THREADS, Threads::fill(workSeq)};
|
||||
|
||||
// after that barrier, all threads send back their allocation
|
||||
afterThread.sync();
|
||||
|
|
|
|||
|
|
@ -145915,7 +145915,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
<node CREATED="1777024713120" ID="ID_24309556" MODIFIED="1777024728172" TEXT="eigentlich keine Anpassung notwendig">
|
||||
<icon BUILTIN="idea"/>
|
||||
</node>
|
||||
<node CREATED="1777024729820" ID="ID_1492141284" MODIFIED="1777024764811" TEXT="man könnte....">
|
||||
<node BACKGROUND_COLOR="#d2beaf" COLOR="#5c4d6e" CREATED="1777024729820" ID="ID_1492141284" MODIFIED="1778788703833" TEXT="man könnte....">
|
||||
<icon BUILTIN="hourglass"/>
|
||||
<node CREATED="1777024765911" ID="ID_596148209" MODIFIED="1777024771000" TEXT="Einträge stehen lassen"/>
|
||||
<node CREATED="1777024772191" ID="ID_645148091" MODIFIED="1777024802721" TEXT="erneute Allokation mit gleicher Adresse aus FREE-State zulassen"/>
|
||||
</node>
|
||||
|
|
@ -145924,8 +145925,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
<icon BUILTIN="yes"/>
|
||||
<node CREATED="1777050440887" ID="ID_1006627416" MODIFIED="1777066692859" TEXT="Verhalten spezifizieren">
|
||||
<icon BUILTIN="yes"/>
|
||||
<node CREATED="1777050520227" ID="ID_796153621" MODIFIED="1777325912544" TEXT="Use-Cases">
|
||||
<linktarget COLOR="#36506f" DESTINATION="ID_796153621" ENDARROW="Default" ENDINCLINATION="-452;0;" ID="Arrow_ID_1304449539" SOURCE="ID_1675354420" STARTARROW="Default" STARTINCLINATION="-32;-602;"/>
|
||||
<node CREATED="1777050520227" ID="ID_796153621" MODIFIED="1778788680440" TEXT="Use-Cases">
|
||||
<linktarget COLOR="#36506f" DESTINATION="ID_796153621" ENDARROW="Default" ENDINCLINATION="-246;327;" ID="Arrow_ID_1304449539" SOURCE="ID_1675354420" STARTARROW="Default" STARTINCLINATION="-44;-241;"/>
|
||||
<node CREATED="1777068198295" ID="ID_1010651012" MODIFIED="1777068390012" TEXT="Allokationsbedarf ankündigen">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head/>
|
||||
|
|
@ -145954,7 +145955,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
<icon BUILTIN="yes"/>
|
||||
</node>
|
||||
</node>
|
||||
<node COLOR="#338800" CREATED="1777050453251" ID="ID_1529547535" MODIFIED="1777317280989" TEXT="Implementieren">
|
||||
<node COLOR="#338800" CREATED="1777050453251" FOLDED="true" ID="ID_1529547535" MODIFIED="1778788655476" TEXT="Implementieren">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node COLOR="#338800" CREATED="1777052048904" ID="ID_1403107789" MODIFIED="1777084853433" TEXT="die Eingangs-Queue wird komplett (transparent) integriert">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
|
|
@ -146162,8 +146163,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
</node>
|
||||
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1776819109351" ID="ID_1998897998" MODIFIED="1777339324992" TEXT="EngineBufferManager">
|
||||
<icon BUILTIN="yes"/>
|
||||
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1776819156872" ID="ID_1476925601" MODIFIED="1777339332224" TEXT="Grundkonzeption schaffen">
|
||||
<icon BUILTIN="pencil"/>
|
||||
<node COLOR="#338800" CREATED="1776819156872" ID="ID_1476925601" MODIFIED="1778788622379" TEXT="Grundkonzeption schaffen">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node CREATED="1777325396485" ID="ID_947969066" MODIFIED="1777325400377" TEXT="Interface">
|
||||
<node CREATED="1777325407524" ID="ID_1333052454" MODIFIED="1777325421966" TEXT="Zugang via lib::Depend<EngineBufferManager>">
|
||||
<node CREATED="1777325446047" ID="ID_1500798718" MODIFIED="1777325451218" TEXT="geht das überhaupt?"/>
|
||||
|
|
@ -146200,8 +146201,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
<icon BUILTIN="idea"/>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1777325872302" ID="ID_1675354420" MODIFIED="1777325912544" TEXT="Use-Cases">
|
||||
<arrowlink COLOR="#36506f" DESTINATION="ID_796153621" ENDARROW="Default" ENDINCLINATION="-452;0;" ID="Arrow_ID_1304449539" STARTARROW="Default" STARTINCLINATION="-32;-602;"/>
|
||||
<node CREATED="1777325872302" ID="ID_1675354420" MODIFIED="1778788680440" TEXT="Use-Cases">
|
||||
<arrowlink COLOR="#36506f" DESTINATION="ID_796153621" ENDARROW="Default" ENDINCLINATION="-246;327;" ID="Arrow_ID_1304449539" STARTARROW="Default" STARTINCLINATION="-44;-241;"/>
|
||||
<node CREATED="1777326001051" ID="ID_825262980" MODIFIED="1777326053002" TEXT="Allokation(en) asynchron beauftragen"/>
|
||||
<node CREATED="1777326037424" ID="ID_613961876" MODIFIED="1777326058745" TEXT="Allokation(en) synchron beauftragen"/>
|
||||
<node CREATED="1777326109630" ID="ID_1521192845" MODIFIED="1777326120671" TEXT="Allokation asynchron zurückgeben"/>
|
||||
|
|
@ -146212,7 +146213,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1777326654102" ID="ID_499885447" MODIFIED="1777326656831" TEXT="Struktur">
|
||||
<node COLOR="#435e98" CREATED="1777326654102" ID="ID_499885447" MODIFIED="1778788640455" TEXT="Struktur">
|
||||
<icon BUILTIN="forward"/>
|
||||
<node CREATED="1777326657789" ID="ID_831464061" MODIFIED="1777326715033" TEXT="Längerfristig: Policy-based-Design">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head/>
|
||||
|
|
@ -146223,7 +146225,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
</body>
|
||||
</html></richcontent>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1777326753646" ID="ID_448684473" MODIFIED="1777326779620" TEXT="doch zunächst einmal: alles direkt coden">
|
||||
<node COLOR="#435e98" CREATED="1777326753646" ID="ID_448684473" MODIFIED="1778788617177" TEXT="doch zunächst einmal: alles direkt coden">
|
||||
<icon BUILTIN="yes"/>
|
||||
<node CREATED="1777339002955" ID="ID_758544304" MODIFIED="1777339007206" TEXT="typedef BufferAllocator"/>
|
||||
<node CREATED="1777339008730" ID="ID_1534654283" MODIFIED="1777339172707" TEXT="verwende fix eine Tile-Size von 64 Bytes">
|
||||
|
|
@ -146367,7 +146369,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1778118771505" ID="ID_1314536256" MODIFIED="1778118805988" TEXT="also ⟹ sinnvollerweise die Eingangs- und Ausgangs-Seite dediziert und separat bauen">
|
||||
<node BACKGROUND_COLOR="#c8c0b6" COLOR="#435e98" CREATED="1778118771505" ID="ID_1314536256" MODIFIED="1778788608420" TEXT="also ⟹ sinnvollerweise die Eingangs- und Ausgangs-Seite dediziert und separat bauen">
|
||||
<icon BUILTIN="yes"/>
|
||||
<node CREATED="1778118817300" ID="ID_1259663169" MODIFIED="1778118842536" TEXT="Ausgang ≙ Allokations-Requests">
|
||||
<node CREATED="1778118984391" ID="ID_231045425" MODIFIED="1778118994533" TEXT="angefordert wird nur eine size"/>
|
||||
|
|
@ -146409,8 +146411,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1778119450587" ID="ID_1806367243" MODIFIED="1778119470264" TEXT="Anordnung im Code">
|
||||
<icon BUILTIN="flag-pink"/>
|
||||
<node COLOR="#338800" CREATED="1778119450587" FOLDED="true" ID="ID_1806367243" MODIFIED="1778788593642" TEXT="Anordnung im Code">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node CREATED="1778119474043" ID="ID_898200002" MODIFIED="1778119501037">
|
||||
<richcontent TYPE="NODE"><html>
|
||||
<head/>
|
||||
|
|
@ -146431,7 +146433,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
<node CREATED="1778119639862" ID="ID_1359278383" MODIFIED="1778119728301" TEXT="Konsequenz: der EngineBufferManager muß den LocalBufferStore sehen"/>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1778199712170" ID="ID_334307615" MODIFIED="1778199836250" TEXT="auflösen durch getrennte Translation-Units">
|
||||
<node COLOR="#435e98" CREATED="1778199712170" ID="ID_334307615" MODIFIED="1778788535095" TEXT="auflösen durch getrennte Translation-Units">
|
||||
<icon BUILTIN="yes"/>
|
||||
<node COLOR="#5b280f" CREATED="1778199840557" ID="ID_444542765" MODIFIED="1778200791662" TEXT="hier nochmal die Diskussion: Abstraktion einführen?">
|
||||
<linktarget COLOR="#694543" DESTINATION="ID_444542765" ENDARROW="Default" ENDINCLINATION="-442;-19;" ID="Arrow_ID_1733074661" SOURCE="ID_845392021" STARTARROW="None" STARTINCLINATION="183;9;"/>
|
||||
<icon BUILTIN="help"/>
|
||||
|
|
@ -146451,7 +146454,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
</html></richcontent>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#f0d5c5" COLOR="#990033" CREATED="1778200776431" ID="ID_633380599" MODIFIED="1778200789719" TEXT="damit dann die Frage: wer steht »vorne«?">
|
||||
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#3f0f69" CREATED="1778200776431" ID="ID_633380599" MODIFIED="1778788526626" TEXT="damit dann die Frage: wer steht »vorne«?">
|
||||
<icon BUILTIN="help"/>
|
||||
<node CREATED="1778200986313" ID="ID_833445396" MODIFIED="1778201001726" TEXT="render-environment.cpp : orchestriert alle Komponenten"/>
|
||||
<node CREATED="1778201611985" ID="ID_915606652" MODIFIED="1778201663551" TEXT="local-buffer-store.hpp : muß Interface + Impl sichtbar includieren">
|
||||
|
|
@ -146473,7 +146476,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
<node CREATED="1778201747177" ID="ID_1235232289" MODIFIED="1778201802499" TEXT="⟹ engine-buffer-manager.hpp : sichtbares Backend-Service-Interface"/>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#f8f1cb" COLOR="#a50125" CREATED="1778203953066" ID="ID_570673188" MODIFIED="1778203985427" TEXT="brauche dann aber doch eine Konvention / Abstraktion für den Callback">
|
||||
<node BACKGROUND_COLOR="#c8c0b6" COLOR="#435e98" CREATED="1778203953066" ID="ID_570673188" MODIFIED="1778788509519" TEXT="brauche dann aber doch eine Konvention / Abstraktion für den Callback">
|
||||
<icon BUILTIN="messagebox_warning"/>
|
||||
<node CREATED="1778203987341" ID="ID_384285004" MODIFIED="1778204998986" TEXT="und zwar gibt es eine zusätzliche Komplikation durch das Thread-Local-Slice">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
|
|
@ -146725,8 +146728,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
<linktarget COLOR="#b9104e" DESTINATION="ID_985414238" ENDARROW="Default" ENDINCLINATION="653;64;" ID="Arrow_ID_649016741" SOURCE="ID_398699629" STARTARROW="Default" STARTINCLINATION="646;0;"/>
|
||||
<linktarget COLOR="#fe590e" DESTINATION="ID_985414238" ENDARROW="Default" ENDINCLINATION="0;-19;" ID="Arrow_ID_928400072" SOURCE="ID_1297893109" STARTARROW="None" STARTINCLINATION="64;21;"/>
|
||||
<linktarget COLOR="#b9104e" DESTINATION="ID_985414238" ENDARROW="Default" ENDINCLINATION="653;64;" ID="Arrow_ID_649016741" SOURCE="ID_398699629" STARTARROW="Default" STARTINCLINATION="646;0;"/>
|
||||
<icon BUILTIN="broken-line"/>
|
||||
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1778684714648" HGAP="44" ID="ID_314091864" MODIFIED="1778692175930" TEXT="genaue Anforderungs-Analyse" VSHIFT="2">
|
||||
<linktarget COLOR="#dd0609" DESTINATION="ID_314091864" ENDARROW="Default" ENDINCLINATION="21;-45;" ID="Arrow_ID_425697321" SOURCE="ID_494756239" STARTARROW="None" STARTINCLINATION="-13;40;"/>
|
||||
|
|
@ -146938,8 +146941,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
im Grunde müßte man nur den Zugang zum Memory-Manager selber locken, denn die ganzen Queue-Operationen sind ja schon lock-free und threadsafe; auch könnten diese Locks dann non-recursive sein
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
</html></richcontent>
|
||||
</node>
|
||||
<node CREATED="1778630869382" ID="ID_1688710426" MODIFIED="1778631446175" TEXT="möglichst wenig Locks">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
|
|
@ -146949,8 +146951,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
...andererseits ist jeder (Mutex)-Lock ein Kernel-Aufruf, und wir erwarten durchaus ganze Bündel an Requests. Außerdem wird mit wenigen Locks auf API-Level viel klarer, daß das Locking vollständig, und der Zustand konsistent ist.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
</html></richcontent>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#ccb59b" COLOR="#6e2a38" CREATED="1778631447430" ID="ID_1196517656" MODIFIED="1778631809891" TEXT="entscheide mich (nach Bauchgefühl) für »wenig Locks«">
|
||||
|
|
@ -146967,16 +146968,15 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
Den Ausschlag gegeben hat aber eine andere Hinsicht: Klarheit und Wartbarkeit sind wichtiger, spezielle Performance-Tweaks sollte man empirisch beweisen.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
</html></richcontent>
|
||||
<font ITALIC="true" NAME="SansSerif" SIZE="14"/>
|
||||
<icon BUILTIN="yes"/>
|
||||
</node>
|
||||
<node CREATED="1778631791231" ID="ID_118677112" MODIFIED="1778631843980" TEXT="in #1429 notiert"/>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1777324560690" ID="ID_318881353" MODIFIED="1778507667074" TEXT="Rahmen per Test abstecken">
|
||||
<icon BUILTIN="pencil"/>
|
||||
<node COLOR="#338800" CREATED="1777324560690" ID="ID_318881353" MODIFIED="1778788472215" TEXT="Rahmen per Test abstecken">
|
||||
<icon BUILTIN="yes"/>
|
||||
<node COLOR="#9b0b61" CREATED="1778591274938" ID="ID_540360328" MODIFIED="1778591296515" TEXT="EngineBufferManager_test">
|
||||
<icon BUILTIN="info"/>
|
||||
</node>
|
||||
|
|
@ -147021,8 +147021,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
<node CREATED="1778635075937" ID="ID_1005055901" MODIFIED="1778635080660" TEXT="alles wieder freigeben"/>
|
||||
<node CREATED="1778635083791" ID="ID_227876159" MODIFIED="1778635088455" TEXT="Diagnostik prüfen"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1778599975152" ID="ID_1790536650" MODIFIED="1778641424697" TEXT="verify_asyncRequest">
|
||||
<icon BUILTIN="pencil"/>
|
||||
<node COLOR="#338800" CREATED="1778599975152" ID="ID_1790536650" MODIFIED="1778788462054" TEXT="verify_asyncRequest">
|
||||
<node CREATED="1778641426736" ID="ID_460139955" MODIFIED="1778641441187" TEXT="sollte hier gleich etwas »massives« machen"/>
|
||||
<node CREATED="1778641442270" ID="ID_1826595532" MODIFIED="1778641456169" TEXT="führe eine typische usage-sequence in jedem Thread aus">
|
||||
<node CREATED="1778641462236" ID="ID_51963277" MODIFIED="1778641469666" TEXT="fordere eine Allokation"/>
|
||||
|
|
@ -147035,16 +147034,16 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
<node CREATED="1778641528055" ID="ID_572369413" MODIFIED="1778641532894" TEXT="— delay —"/>
|
||||
<node CREATED="1778641533746" ID="ID_1255059129" MODIFIED="1778641540762" TEXT="schicke die Allokation zurück"/>
|
||||
</node>
|
||||
<node CREATED="1778641582324" ID="ID_723485846" MODIFIED="1778641604860" TEXT="danach sollten....">
|
||||
<node BACKGROUND_COLOR="#c8c0b6" COLOR="#435e98" CREATED="1778641582324" ID="ID_723485846" MODIFIED="1778788451349" TEXT="danach sollten....">
|
||||
<node CREATED="1778641588959" ID="ID_857089658" MODIFIED="1778641600492" TEXT="NUM_THREADS Allokationen erfolgt sein"/>
|
||||
<node CREATED="1778641607696" ID="ID_514432880" MODIFIED="1778641618195" TEXT="alle diese Allokationen wieder in der Queue sitzen"/>
|
||||
<node COLOR="#435e98" CREATED="1778642703060" ID="ID_378615107" MODIFIED="1778694539272" TEXT="funktioniert nicht...">
|
||||
<node COLOR="#435e98" CREATED="1778642703060" FOLDED="true" ID="ID_378615107" MODIFIED="1778788384414" TEXT="funktioniert nicht...">
|
||||
<icon BUILTIN="broken-line"/>
|
||||
<node CREATED="1778642715157" ID="ID_723545359" MODIFIED="1778694786375" TEXT="Beobachtungen">
|
||||
<icon BUILTIN="info"/>
|
||||
<node CREATED="1778642721100" ID="ID_454509509" MODIFIED="1778642737405" TEXT="einige Threads sind anscheinend schon fertig bevor andere überhaupt erst anfangen"/>
|
||||
<node CREATED="1778642739266" ID="ID_1618120303" MODIFIED="1778642753195" TEXT="damit werden natürlich einige Allokationen bereits weggeräumt"/>
|
||||
<node BACKGROUND_COLOR="#f0d5c5" COLOR="#990033" CREATED="1778642777488" ID="ID_735364209" MODIFIED="1778642799183" TEXT="unklar: warum sind nach processPending() noch Allocs offen?">
|
||||
<node BACKGROUND_COLOR="#d3a387" COLOR="#990033" CREATED="1778642777488" ID="ID_735364209" MODIFIED="1778788369851" TEXT="unklar: warum sind nach processPending() noch Allocs offen?">
|
||||
<icon BUILTIN="help"/>
|
||||
</node>
|
||||
</node>
|
||||
|
|
@ -147063,8 +147062,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
Jedenfalls in dem Setup, das ich jetzt hier für diesen neuen Test gebaut habe, verwenden alle Threads dasselbe Lamba für die Thread-Funktion <i>gemeinsam</i>. Das bedeutet, das makeRandomGen() wird tatsächlich nur einmal aufgerufen, und daher haben wir nur einen einzigen Seed (selbst wenn die Lambdas dann im Thread-Wrapper nochmal kopiert werden)
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
</html></richcontent>
|
||||
<icon BUILTIN="messagebox_warning"/>
|
||||
<node CREATED="1778643262156" ID="ID_46315611" MODIFIED="1778643274742" TEXT="sehe in allen Threads exakt den gleichen Delay"/>
|
||||
<node CREATED="1778643275676" ID="ID_284051539" MODIFIED="1778643284600" TEXT="daher wachen die auch alle gemeinsam auf"/>
|
||||
|
|
@ -147090,13 +147088,16 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
</node>
|
||||
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1778681131699" ID="ID_725298648" MODIFIED="1778681149966" TEXT="nebenbei bemerkt: das API für ScopedCollection ist unvollständig">
|
||||
<icon BUILTIN="broken-line"/>
|
||||
<node CREATED="1778681154739" ID="ID_1527328731" MODIFIED="1778681164103" TEXT="das Erzeugen der Instanz ist zu umständlich"/>
|
||||
<node CREATED="1778681181873" ID="ID_262040421" MODIFIED="1778709901385" TEXT="es fehlt das Hinterlegen fester Argumente">
|
||||
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1778681154739" ID="ID_1527328731" MODIFIED="1778783685746" TEXT="das Erzeugen der Instanz ist zu umständlich">
|
||||
<icon BUILTIN="messagebox_warning"/>
|
||||
</node>
|
||||
<node COLOR="#338800" CREATED="1778681181873" ID="ID_262040421" MODIFIED="1778783692002" TEXT="es fehlt das Hinterlegen fester Argumente">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node CREATED="1778709995012" ID="ID_1485556701" MODIFIED="1778710018906" TEXT="wäre möglich durch Erweiterung von FillWith">
|
||||
<node CREATED="1778710037003" ID="ID_730979662" MODIFIED="1778710085583" TEXT="müßte dazu ein Argument-Tupel materialisieren"/>
|
||||
<node CREATED="1778710769213" ID="ID_410726998" MODIFIED="1778710789818" TEXT="bisher wird <tuple> nicht includiert"/>
|
||||
</node>
|
||||
<node CREATED="1778711002374" ID="ID_839332664" MODIFIED="1778711024630" TEXT="könnte man auch als λ-builder neu formulieren">
|
||||
<node COLOR="#435e98" CREATED="1778711002374" ID="ID_839332664" MODIFIED="1778783672948" TEXT="könnte man auch als λ-builder neu formulieren">
|
||||
<node CREATED="1778711483235" ID="ID_1590205155" LINK="https://stackoverflow.com/a/49902823/444796" MODIFIED="1778711541326" TEXT="in C++20 gibt es perfect λ-capture"/>
|
||||
<node CREATED="1778711904212" ID="ID_1775952563" LINK="https://en.cppreference.com/cpp/language/lambda#Lambda_capture" MODIFIED="1778711918406" TEXT="ist möglich sowohl für Referenz, alsauch Copy-Capture"/>
|
||||
</node>
|
||||
|
|
@ -147104,14 +147105,51 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
<icon BUILTIN="idea"/>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1778681164947" ID="ID_281208279" MODIFIED="1778681175454" TEXT="es fehlt das Aufrufen eines Lambda"/>
|
||||
<node CREATED="1778727780762" ID="ID_1017169041" MODIFIED="1778727790117" TEXT="Iterator-pull ist limitiert">
|
||||
<node COLOR="#338800" CREATED="1778681164947" ID="ID_281208279" MODIFIED="1778783693544" TEXT="es fehlt das Aufrufen eines Lambda">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node COLOR="#338800" CREATED="1778727780762" ID="ID_1017169041" MODIFIED="1778783695258" TEXT="Iterator-pull ist limitiert">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node CREATED="1778727795148" ID="ID_699461971" MODIFIED="1778727812229" TEXT="könnte sowohl copy-Konstruktion, alsauch Value-Konstruktion machen"/>
|
||||
<node CREATED="1778727813246" ID="ID_791306906" MODIFIED="1778727831783" TEXT="sollte mit dem Populieren aufhören, wenn der Iterator erschöpft ist"/>
|
||||
<node COLOR="#435e98" CREATED="1778727839622" ID="ID_576015934" MODIFIED="1778727896555" TEXT="Mit einem Concept könnte das direkt eine Konstruktor-Variante sein">
|
||||
<icon BUILTIN="idea"/>
|
||||
</node>
|
||||
</node>
|
||||
<node COLOR="#435e98" CREATED="1778783706927" ID="ID_909107103" MODIFIED="1778788348871" TEXT="Formulierung diverser Tests nun stark vereinfacht">
|
||||
<icon BUILTIN="idea"/>
|
||||
<node CREATED="1778783718253" ID="ID_271640023" MODIFIED="1778783732071" TEXT="speziell die Fälle wo wir einen Satz an Threads starten"/>
|
||||
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1778788061645" ID="ID_92554859" MODIFIED="1778788084275" TEXT="aber Vorsicht — nicht immer möglich">
|
||||
<icon BUILTIN="messagebox_warning"/>
|
||||
<node CREATED="1778788087357" ID="ID_502254218" MODIFIED="1778788224843" TEXT="manchmal müssen Referenzen an den Konstruktor übergeben werden">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
...und das geht nicht mit dem fill()-Populator, denn dieser materialisiert alle Argumente als Kopien. Man könnte das ändern, müßte dann aber explizit ein perfect-forwarding-Tupel bauen. Das war mir jetzt alles zu mühsam
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#b6020c" CREATED="1778788232367" ID="ID_134775139" MODIFIED="1778788330594" TEXT="manchmal ist es essentiell, für jeden Thread ein separates Lambda zu konstruieren">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
das ist im Besonderen relevant, wenn die captures nicht-triviale Operationen in der Initialisierung verwenden. Da der fill()-Popoulator kopiert, würden sich alle Threads das gleiche Lambda teilen, und daher auf den gleichen Werten arbeiten. Das ist <b>eine subtile Falle</b>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
<icon BUILTIN="clanbomber"/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node COLOR="#435e98" CREATED="1778680084927" ID="ID_831309457" MODIFIED="1778694776376" TEXT="Anpassungen">
|
||||
|
|
@ -147282,9 +147320,10 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1778695283214" ID="ID_978534045" MODIFIED="1778695295656" TEXT="Vorsicht: Testsuite ist auf 8MiB limitiert">
|
||||
<node CREATED="1778695297087" ID="ID_1119237552" MODIFIED="1778695308343" TEXT="dieser test alloziert »mal eben« 100 MiB"/>
|
||||
<node CREATED="1778695331666" ID="ID_1196538153" MODIFIED="1778695331666" TEXT=""/>
|
||||
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1778695283214" ID="ID_978534045" MODIFIED="1778788447228" TEXT="Vorsicht: dieser test alloziert »mal eben« 100 MiB">
|
||||
<icon BUILTIN="messagebox_warning"/>
|
||||
<node CREATED="1778695297087" ID="ID_1119237552" MODIFIED="1778788425737" TEXT="kein Problem ... die Testsuite ist auf 8 GiB limitiert"/>
|
||||
<node CREATED="1778695331666" ID="ID_1196538153" MODIFIED="1778788441455" TEXT="ein moderner PC steckt sowas locker weg"/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
|
|
@ -172016,8 +172055,7 @@ std::cout << tmpl.render({"what", "World"}) << s
|
|||
...nämlich indem der Job letztlich auf den synchronen Fall zurückgreift, dieser aber durch die gleiche Mechanik implementiert ist, wie der asynchrone Fall, und dabei gleich alle aufgesammelten Requests in den Queues mit verarbeitet. Dadruch wird effektiv ein Batch an Allokator-Aufwand in denjenigen Worker geschoben, der rein zufällig als erster auf eine nicht bediente Allokation stößt. Das Schöne an dieser Code-Struktur ist, daß die Möglichkeit offen bleibt, später zur Performance-Optimierung (oder nur in bestimmten Situationen) dennoch etwas speziell einzurichten / schedulen / auszuführen, ohne daß dazu der Code im EngineBufferManager geändert werden muß
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
</html></richcontent>
|
||||
<arrowlink COLOR="#cffd68" DESTINATION="ID_1723782536" ENDARROW="Default" ENDINCLINATION="2031;68;" ID="Arrow_ID_1674470149" STARTARROW="None" STARTINCLINATION="1004;42;"/>
|
||||
<font NAME="SansSerif" SIZE="11"/>
|
||||
<icon BUILTIN="idea"/>
|
||||
|
|
|
|||
Loading…
Reference in a new issue