Commit graph

2445 commits

Author SHA1 Message Date
89dd35e70d Library: cover handling limits for virtual baseclass scenario
building on the preceding analysis, we can now demonstrate that
the container is initially able to grow, but looses this capability
after accepting one element of unknown subclass type...
2024-06-16 04:22:28 +02:00
85e3780a34 Library: reassess logic to reject some types for existing container
`lib::Several` is designed to be highly adaptable, allowing for
several quite distinct usage styles. On the downside, this requires
to perform some checks at runtime only, since the ability to handle
some element depends on specific circumstances.

This is a notable difference to `std::vector`, which is simply not capable
of handling ''non-copyable'' types, even if given an up-front memory reservation.

The last test case provided with the previous changeset did not trigger
an exception, but closer investigation revealed that this is correct,
since in this specific situation the container can accept this object type,
thereby just loosing the ability to move-relocate further objects.

A slightly re-arranged test scenario can be used to demonstrate this fine point.
2024-06-16 04:22:28 +02:00
d9f86ad891 Library: investigate case with known element type
- the test-dummy objects need a `noexcept` move ctor
- **bug** here: need an explicit check to prevent other types
  than the known element type from ''sneaking in''
2024-06-16 04:22:28 +02:00
006809712e Library: some coverage for rejected type placements
The `SeveralBuilder` is very flexible with respect to added elements,
but it will investigate the provided type information and reject any
further build operation that can not be carried out safely.
2024-06-16 04:22:28 +02:00
1169b6272e Library: test coverage for some ''special'' builder usages 2024-06-16 04:22:28 +02:00
601a555e6c Library: builder to add heterogeneous elements
...turns out that we must ensure to pass a plain "object" type
to the standard allocator framework (no const, no references).
Here, ''object in C++ terminology'' means a scalar or record type,
but no functor, no references and no void,
2024-06-16 04:22:28 +02:00
1a76fb46f3 Library: elaborate SeveralBuilder operations
Consider what (not) to support.
Notably I decided ''not to support'' moving out of an iterator,
since doing so would contradict the fundamental assumptions of
the »Lumiera Forward Iterator« Concept.

Start verifying some variations of element placement,
still focussing on the simple cases
2024-06-16 04:22:28 +02:00
773325f1bc Library: rearrange strategy code
Parts of the decision logic for element handling was packaged
as separate »strategy« class — but this turned out to be neither
a real abstraction, nor configurable in any way. Thus it is better
to simplify the structure and turn these type predicates into simple
private member functions of the SeveralBuilder itself
2024-06-16 04:22:28 +02:00
6f3bfb5ff3 Library: better alignment handling
Elements maintained within the storage should be placed such
as to comply with their alignment requirements; the element spacing
thus must be increased to be a multiple of the given type's alignment.

This solution works in most common cases, where the alignement is
not larger as the platform's bus width (typically 64bit); but for
''over-aligned types'' this scheme may still generate wrong object
start positions (a completely correct solution would require to
add a fixed offset to the beginning of the storage array and also
to capture the alignment requirements during population and to
re-check for each new type.
2024-06-16 04:22:28 +02:00
66a1f6f8ab Library: add iteration capability to the Several-container
...and the nice thing is, the recently built `IterIndex` iteration wrapper
covers this functionality right away, simply because `lib::Several`
is a generic container with subscript operator.
2024-06-16 04:22:27 +02:00
a3e8579e4a Library: basic functionality of the Several-container working
...passes the simplest unit test
 * create a Several<int>
 * populate from `std::initializer_list`
 * random-access to elements

''next step would be to implement iteration''
2024-06-16 04:22:27 +02:00
8534914c71 Library: work out a solution how to store a Deleter functor
After some fruitless attempts, I settled for using std::function directly,
in order to establish a working baseline of this (tremendously complicated)
allocation logic. Storing a std::function in the ArrayBucket is certainly
wasteful (it costs 4 »slots« of memory), but has the upside that
it handles all those tricky corner cases magically; notably
the functor can be stored completely inline in the most relevant
case where the allocator is a monostate; moreover we bind a lambda,
which can be optimised very effectively, so that in the simplest case
there will be only the single indirection through the ''invoker''.

This **completes the code path for a simple usage cycle**

🠲 ''and hooray ... the test crashes with a double-free''
2024-06-09 23:45:24 +02:00
954a399b1c Library: logic to select a suitable deleter
- ensure the ''deleter function'' is invoked
- care for proper ''deleter'' setup in case of exception while copying
- need to »lock-in« on one specific kind of ''destructor invocation scheme,''
  since we do not keep track of individual concrete element types
2024-06-09 17:19:40 +02:00
54bd568714 Library: integrate storage-management logic
Parts of this logic were first coded down in the `realloc` template method,
where it did not really belong; thus reintegrate similar logic one level above,
in the SeveralBuilder::adjustStorage(). Moreover, for performance reasons,
always start with an initial chunk, similar to what `std::vector` does...
2024-06-09 03:00:12 +02:00
e99f4d531b Library: simplified and generic realloc
since this is meant as a policy implementation, reduce it to the bare operation;
the actual container storage handling logic shall be implemented in the container
and based on those primitive and configurable base operations
2024-06-09 02:06:41 +02:00
3a263c8c63 Library: rearrange logic for trivial move detection
...still fighting to get the design of the `AllocationPolicy`
settled to work well with `AllocationCluster` while also allowing
to handle data types which are (not) trivially copyable.

This changeset attempts to turn the logic round: now we capture
an ''move exclusion flag'' and otherwise allow the Policy to
decide on its own, based on the ''element type''
2024-06-09 00:58:42 +02:00
446f133c09 Library: logic to accept further elements
- verifies if new element can just fit in
- otherwise ensure the storage adjustments are basically possible
- throw exception in case the new element can not be accommodated
- else request possible storage adjustments
- and finally let the allocator place the new element
2024-06-08 17:35:14 +02:00
0a788570a9 Library: integrate strategy for acceptable element types
Draft skeleton of the logic for element creation.
This turns out to be a rather challenging piece of code,
since we have to rely on logical reasoning about properties
of the element types in order to decide if and how these
elements can be emplaced, including the possibility to
re-allocate and move existing data to a new location.

- if we know the exact element type, we can handle any
  copyable or movable object
- however, if the container is filled with a mixture of types,
  we can not re-allocate or grow dynamically, unless all data
  is trivially copyable (and can thus be handled through memmove)
- moreover we must ensure the ability to invoke the proper destructor
2024-06-08 03:52:05 +02:00
bbec35ce65 Library: switch rest of implementation
...and remove now obsolete metadata fields in the collection and builder classes
2024-06-08 02:03:07 +02:00
deaabcda6e Library: adapt allocation and realloc to new layout
significantly simplifies both API and calculations,
since all necessary data is now within the ArrayBucket
2024-06-08 01:50:50 +02:00
130a021020 Library: rearrange storage layout
In-depth analysis of storage management revealed a misconception
with respect to possible storage optimisations, requiring more
metadata fields to handle all corner cases correctly.

It seems prudent to avoid any but the most obvious optimisations
and wait for real-world usage for a better understanding of the
prevalent access patterns. However, in preparation for any future
optimisations, all access coordination and storage metadata is
now relocated into the `ArrayBucket`, and thus resides within the
managed allocation, allowing for localised layout optimisations.

To place this into context: the expected prevalent use case is
for the »Render Nodes Network«, which relies on `AllocationCluster`
for storage management; most nodes will have only a single predecessor
or successor, leading to a large number of lib::Several intsances
populated with a single data element. In such a scenario, it is
indeed rather wasteful to allocate four »slot« of metadata for
each container instance; even more so since most of this
metadata is not even required in such a scenario.
2024-06-08 00:23:42 +02:00
154a7018be Library: attempt to build a re-alloc on top of the new adapter
...which basically ''seems doable'' now, yet turns up several unsolved problems
- need a way to handle excess storage for the raw allocation
- generally should relocate all metadata into the ArrayBucket
- mismatch at various APIs; must re-think where to pass size explicitly
- unclear yet how and where to pass the actual element type to create
2024-06-07 19:04:06 +02:00
24b3e5ceba Library: work out adaptor solution for custom allocator
...turns out to be rather challenging, due to the far reaching requirements
 * the default case (heap allocation) ''must work out-of-the box''
 * optionally a C++ standard conformant `Allocator` can be adapted
 * which works correct even in case this allocator is ''not a monostate''
 * **essential requirement** is to pass an `AllocationCluster` reference directly
 * need a ''generic extension point'' to adapt to similar elaborate custom schemes

__Note__: especially we want to create a direct collaboration between the allocation policy and the underlying allocator to allow support for a dedicate ''realloc operation''
2024-06-07 03:36:25 +02:00
c5d1a7d0df Library: rearrange into standard allocation factory
- code spelled out as intended, according to generic scheme
 - can now encode the »unmanaged« case directly as `null`-deleter,
   because in all other cases a deleter function is mandatory now
 - add default constructor to `ArrayBucket`, detailing the default spread
2024-06-07 01:53:38 +02:00
0e2ca6ee1c Library: consider to align this with the »Factory« concept
even while at first sight only a ''deleter instance'' is required,
it seems prudent to rearrange the code in accordance to the prospective
Allocator / Object Factory concept, and rather try to incorporate
the specifics of the memory layout into this generic view, thereby
abstracting the actual allocator away.

This can be achieved by using a standard-allocator for `std::byte`
as the base allocator and treat each individual element allocator
as a specialised cross-allocator (assuming that this cross adaptation
is actually trivial in almost all cases)
2024-06-07 01:14:55 +02:00
bf74ba6292 Library: sketch for a deleter trampoline
for simple allocators this can be static
2024-06-06 18:41:07 +02:00
98d5b2962c Library: analyse options for passing a deleter function
The fundamental decision is that we want to have a single generic front-end,
meaning that we must jump dynamically into a configured deleter function.
And on top of that comes the additional requirement that ''some allocators''
are in fact tied to a specific instance, while other allocators are monostate.

However, we can distinguish both by probing if the allocator can be default constructed,
and if a default constructed allocator is equivalent to the currently used alloctor instance.

If this test fails, we must indeed maintain a single allocator instance,
and (to avoid overengineering for this rather special use case) we will
place this allocator instance into heap memory then, with a self-cleanup mechanism
On the other hand, all monostate allocators can be handled through static trapolines.
2024-06-06 02:46:05 +02:00
802fef9b7c Library: work out Skeleton for memory-handling strategy
- the basic decision is to implement ''realloc'' similar to `std::vector`
- however the situation is complicated by the desire to allow arbitrary element types
- ⟹ must build a strategy based on the properties of the target type
- the completely dynamic growth is only possibly for trivially-movable types
- can introduce a dedicated ''element type'' though, and store a trampolin handler
2024-06-05 02:24:39 +02:00
2abbae77d7 Library: draft memory rearrangements
not clear yet how to handle the classical "realloc" situation
2024-05-29 01:01:16 +02:00
feeee4096d Library: draft skeleton of builder operations
- create by forwarding allocator arguments to policy
- builder-Op to append from iterator
- decide to collapse the ArrayBucket class, since
  access is going through unsafe pointer arithmetic anyway
2024-05-28 18:52:01 +02:00
27b36f0679 Library: implement access to storage and subscript
...not sure if this approach works out OK,
since we can not make a safe downcast to a size known at runtime
2024-05-28 18:07:08 +02:00
f6e4358259 Library: data layout for the new Several container
- favour dynamic polymorphism
- use additional memory for management data alongside the element allocation
- encode a flag and a deleter pointer to enable ownership of the allocation
- inherit base container privately into builder, so the build ends with a slice
2024-05-28 17:20:34 +02:00
73dd24ecef Library: start design draft to replace RefArray
Some decisions
 - use a single template with policy base
 - population via separate builder class
 - implemented similar to vector (start/end)
 - but able to hold larger (subclass) objects
2024-05-28 04:03:51 +02:00
e4f91ecb4d Library: document usage of AllocationCluster for STL containers
- basically works out-of-the-box now
- the hard wired fixed Extent size is a serious limitation
- however, this is not the intended primary use, rather complementary
2024-05-28 00:36:32 +02:00
178107e8b9 Library: enable empty-base optimisation for allocator
...this is an important detail: quite commonly, a custom allocator
is actually implemented as monostate, to avoid bloating every client container
with a backlink pointer; by inheriting the `StdFactory` adapter from the
allocator, the empty-base optimisation can be exploited.

In the standard case thus LinkedElements is the same size as a single
pointer, which is already exploited at several places in the code base.
Notably `AllocationCluster` uses a »virtual overlay« to dress-up the
position pointer as `LinkedElements`, allowing to delegate most of the
administration and memory management to existing and verified code.


With this adjustments, `LinkedElements` pass the tests again
and the rework of `AllocationCluster` is considered complete.
2024-05-27 19:02:31 +02:00
cf80a292c1 Library: adapt LinkedElements to use the new StdFactory adaptor
This is the first validation of the new design:
the policy to take ownership can be reimplemented simply
by delegating to the adaptor for a C++ standard allocator
2024-05-27 02:06:06 +02:00
8ca3c61c6f Library: Analysis and planning towards a generic Allocator Concept
The following structure can be expected, after __switching to C++20__
 * Concept **Allocator** deals with the bare memory allocation
 * Concept **Factory** handles object creation and disposal by delegation
 * Concept **Handle** is a ready-made functor for dependency-injection

Right now, an implementation of the ''prospective Factory Concept''
can be provided, by delegating through `std::allocator_traits` to a given
`std::allocator` or compatible object
2024-05-26 19:31:30 +02:00
5259000bc4 Library: how to use a standard allocator for LinkedElements
By default, LinkedElements uses a policy OwningHeapAllocated;
while retaining this interface, this policy should be recast
to rely on a standard compliant allocator, with a default
fallback to `std::allocator<T>`

This way, a single policy would serve all the cases where
objects are actually owned and managed by `LinkedElements`,
and most special policies would be redundant.

This turns out to be quite tedious and technical however,
since the newer standard mandates to use std::allocator_traits
as front-end, and moreover the standard allocators are always
tied to one specific target type, while `LinkedElements` is
deliberately used to maintain a polymorphic sequence.
2024-05-26 02:51:47 +02:00
08e0f52e61 Library: low-level implementation internals covered
...including overflow into new extents, alignment padding
and chaining and invocation of destructors.
2024-05-25 20:01:23 +02:00
be398e950a Library: better let C++ handle the destructors
...what I've implemented yesterday is effectively the same functionality
as provided automatically by the C++ object system when using a virtual destructor.
Thus a much cleaner solution is to turn `Destructor` into a interface
and let C++ do all the hard work.

Verified in test: works as intended
2024-05-25 19:27:17 +02:00
71d5851701 Library: implement optional invocation of destructors
This is the first draft, implementing the invocation explicitly
through a trampoline function. While it seems to work,
the formulation can probably be simplified....
2024-05-25 05:14:36 +02:00
31f8664725 Library: verify overflow to second extent 2024-05-25 01:28:29 +02:00
841234684b Library: verify further simple allocations and alignment 2024-05-24 19:06:33 +02:00
037a5f2dd0 Library: verify the simplest possible allocation
...by inspecting the raw memory locations -- looks good thus far...
2024-05-24 18:05:21 +02:00
e8d9dcd9bf Library: implement accounting of storage size
These diagnostics helpers must rely on low-level trickery,
since the implementation strives at avoiding unnecessary storage overhead.
Since `AllocationCluster` is move-only (for good reasons) and `StorageManager`
can not be constructed independently, a »backdoor« is created by
forced cast, relying on the known memory layout
2024-05-24 17:05:50 +02:00
13e22f315a Library: clarify details of the low-level allocation
- rather accept hard-wired limits than making the implementation excessively generic
- by exploiting the layout, the administrative overhead can be reduced significantly
- the trick with the "virtual managment overlay" allows to hand-off most of the
  clean-up work to C++ destructor invocation
- it is important to verify these low-level arrangements explicitly by unit-test
2024-05-19 17:53:51 +02:00
72aea53ac3 Library: idea for an extent management mechanism
* this is pure old-style low-level trickery
 * using a layout trick, the `AllocationCluster`
   can be operated with the bare minimum of overhead
 * this trick relies on the memory layout of `lib::LinkedElements`
2024-05-18 02:35:20 +02:00
c623298ac8 Library: draft standard case for actual memory allocation
- attempt to use the minimal possible storage for the management itself
- rely on the `std::align()` function to do the actual placement
2024-05-16 02:47:01 +02:00
eeda3aaa56 Library: remove elaborate allocation logic
...due to the decision to use a much simpler allocation scheme
to increase probability for actual savings, after switching the API
and removing all trading related aspects, a lot of further code is obsoleted
2024-05-16 01:46:41 +02:00
13f51c910c Library: work out ramifications of the changed design
Notably this raises the difficult question,
whether to ensure **invocation of destructors**.

Not invoking dtors ''breaks one of the most fundamental contracts''
of the C++ language — yet the infrastructure to invoke dtors in such
a heterogeneous cluster of allocations creates a hugely significant
overhead and is bound to poison the caches (objects to be deallocated
typically sit in cold memory pages).

What makes this decision especially daunting is the fact that the
low-level-Model can be expected to be one of the largest systemic
data structures (letting aside the media buffers).

I am leaning towards a compromise: turn down this decision
towards the user of the `AllocationCluster`
2024-05-15 19:59:05 +02:00
fd49c68096 Library: draft a simpler STL complient allocation interface
After some analysis, it became clear that the existing code for
`AllocationCluster` (while in itself valid) will likely miss the point
for the expected usage in the low-level Model: most segments of the
model will be rather small, and thus there is not enough potential for
amortisation when using such a per-type and per-segment scheme;
a rather simplistic linear allocator will be sufficient.

On the other hand, with the current C++ standard it is easy to provide
a complient allocator implementation for STL containers, and thus the
interface should be retro-fitted accordingly.
2024-05-15 01:38:16 +02:00
db30da90ce Invocation: consider storage and allocation of fan-in/fan-out
At the time of the initial design attempts, I naively created a
classic interface to describe an fixed container allocated ''elsewhere.''

Meanwhile the C++ language has evolved and this whole idea looks
much more as if it could be a ''Concept'' (C++20). Moreover, having
several implementations of such a container interface is deemed inadequate,
since it would necessitate ''at least two indirections'' — while
going the Concept + Template route would allow to work without any
indirection, given our current understanding that the `ProcNode` itself
is ''not an interface'' — rather a building block.
2024-05-13 18:34:42 +02:00
c0d5341b15 Invocation: capture idea for sharpened invocation structure
- the starting point is the idea to build a dedicated ''turnout system''
- `StateAdapter`, `BuffTable` ⟶ `FeedManifold` and _Invocation_ will be fused
- actually, the `TurnoutSystem` will be ''pulled'' and orchestrate the invocation
- the structure is assumed to be recursive

The essence of the Node-Invocation, as developed 2009 / 2011 remains intact,
yet it will be organised along a clearer structure
2024-05-12 17:27:07 +02:00
bd9527716a Invocation: segregate first and second buffer feed implementation
Within the existing body of code, there are two unfinished attempts
towards building a node invocation and management of data buffers.

The first attempt was entirely driven from the angle of invoking a
processing function, while the second one draws from a wider scope
and can be considered the solution to build upon regarding data buffers
in general. However, the results of the first approach are well suited
for their specific purpose, so both solutions will be combined.

Thus the arrangement of data feeds going in and out of the render node
shall be renamed into `BuffTable` -> `FeedManifold`
2024-05-11 17:06:12 +02:00
9a435a667e Invocation: start with some rename-refactorings
... to plot a clearer understanding of the intended usage
2024-05-11 16:39:58 +02:00
bb3d565436 Invocation: Reassessment of existing code
...which seems to be basically fine thus far
...beyond some renaming and rearranging

''it turns out that the final, crucial links,
necessary to tie all together, are yet to be developed''
2024-05-05 15:12:23 +02:00
a11ee34fc8 Invocation: forge a path for integration
Facing quite some difficulties here, since there are (at least)
two abandoned past efforts towards building a render node network
in the code base; the structure and architecture decisions from these
previous attempts seem largely valid still, yet on a technical level,
the style of construction evolved considerably in the meantime. Moreover,
these old fragments of code, written during the early stages of the
project, were lacking clear goals and anchor points at places;
the situation is quite different now in this respect.

Sticking to well proven practice, the rework will be driven by a test setup,
and will progress over three steps with increasing levels of integration.
2024-04-23 01:13:40 +02:00
47e26e2a65 Invocation: initial considerations...
Looks like some code archaeology is required
to sort apart the various effort to get this topic started....
2024-04-21 02:58:30 +02:00
d71eb37b52 Scheduler-test: complete and document stress testing effort (closes #1344)
The initial effort of building a Scheduler can now be **considered complete**
Reaching this milestone required considerable time and effort, including
an extended series of tests to weld out obvious design and implementation flaws.

While the assessment of the new Scheduler's limitation and traits is ''far from complete,''
some basic achievements could be confirmed through this extended testing effort:
 * the Scheduler is able to follow a given schedule effectively,
   until close up to the load limit
 * the ''stochastic load management'' causes some latency on isolated events,
   in the order of magnitude < 5ms
 * the Scheduler is susceptible to degradation through Contention
 * as mitigation, the Scheduler prefers to reduce capacity in such a situation
 * operating the Scheduler effectively thus requires a minimum job size of 2ms
 * the ability for sustained operation under full nominal load has been confirmed
   by performing **test sequences with over 80 seconds**
 * beyond the mentioned latency (<5ms) and a typical turnaround of 100µs per job
   (for debug builds), **no further significant overhead** was found.

Design, Implementation and Testing were documented extensively in the [https://lumiera.org/wiki/renderengine.html#Scheduler%20SchedulerProcessing%20SchedulerTest%20SchedulerWorker%20SchedulerMemory%20RenderActivity%20JobPlanningPipeline%20PlayProcess%20Rendering »TiddlyWiki« #Scheduler]
2024-04-20 01:56:54 +02:00
a46449d5ac Scheduler-test: stable-state run > 1sec
This test completes the stress-testing effort
and summarises the findings
 * Scheduler performs within relevant parameter range without significant overhead
 * Scheduler can operate with full load in stable state, with 100% correct result
2024-04-18 01:39:28 +02:00
177e241060 Scheduler-test: investigate extended loads with different patterns
The behaviour seems consistent and the schedule breaks at the expected point.
At first sight, concurrency seems slightly to low; detailed investigation
however shows that this is due to the structure of the load graph,
and in fact the run time comes close to optimal values.
2024-04-18 01:39:28 +02:00
c934e7f079 Scheduler-test: reduce impact of scale adjustments on breakpoint-search
the `BreakingPoint` tool conducts a binary search to find the ''stress factor''
where a given schedule breaks. There are some known deviations related to the
measurement setup, which unfortunately impact the interpretation of the
''stress factor'' scale. Earlier, an attempt was made, to watch those factors
empirically and work a ''form factor'' into the ''effective stress factor''
used to guide this measurement method.

Closer investigation with extended and elastic load patters now revealed
a strong tendency of the Scheduler to scale down the work resources when not
fully loaded. This may be mistaken by the above mentioned adjustments as a sign
of a structural limiation of the possible concurrency.

Thus, as a mitigation, those adjustments are now only performed at the
beginning of the measurement series, and also only when the stress factor
is high (implying that the scheduler is actually overloaded and thus has
no incentive for scaling down).

These observations indicate that the »Breaking Point« search must be taken
with a grain of salt: Especially when the test load does ''not'' contain
a high degree of inter dependencies, it will be ''stretched elastically''
rather than outright broken. And under such circumstances, this measurement
actually gauges the Scheduler's ability to comply to an established
load and computation goal.
2024-04-18 01:39:27 +02:00
55842a8d4f Scheduler-test: identify and fix bug in allocator
...well — more of a logical contradiction, not so much a bug.
The underlying problematic situation arises when meanwhile the
Extent storage has been expanded, and especially the active slots
are in »wrapped state«. In this case, the newly allocated extents
must be rotated in, which invalidates existing index numbers.

This problem was amended by exploting a chaching mechanism, allowing
to re-attach and validate an index position still stored in an old
iterator; especially this can happen when attempting to attach a
follow-up dependency onto a job planned earlier, but not yet scheduled.

The problem here was an assertion failure, which was triggered with a
high probability; the fix for the problem detailed above used the yield()
function, while it actually was only interested in retrieving the
Extent's address to probe if the extent matches an known storage location.

The solution is to provide a dedicated function for this check, which
can then skip the sanity check (because in this case we do not want
to use the Extent, and thus can touch obsoleted/inactive Extents
without problem)
2024-04-18 01:39:27 +02:00
7c2b9a8ba5 Scheduler-test: investigate extended load patterns
...this seems to be the last topic for this investigation of Scheduler behaviour;
the goal is to demonstrate readiness for stable-state operation over an extended period of time
2024-04-18 01:39:26 +02:00
1d4f6afd18 Scheduler-test: complete and document the Load-peak tests
- use parameters known to produce a clean linear model
- assert on properties of this linear model

Add extended documentation into the !TiddlyWiki,
with a textual account of the various findings,
also including some of the images and diagrams,
rendered as SVG
2024-04-12 02:23:31 +02:00
5b62438eb4 Scheduler-test: investigate logic problem related to the »Tick« deadline
In the end, I decided that it ''is to early to decide anything'' in this respect...

The actual situation encountered is a **Catch-22**:
 * in its current form, the »Tick« handler detects compulsory jobs beyond deadline
 * since such a Job ''must not be touched anymore,'' there is no way scheduling can proceed
 * so this would constitute a ''Scheduler Emergency''
All fine — just the »Tick« handler ''itself is a compulsory job'' — and being a job, it can well be driven beyond its deadline. In fact this situation was encountered as part of stress testing.

Several mitigations or real solutions are conceivable, but in the end,
too little is known yet regarding the integration of the scheduler within the Engine
Thus I'll marked the problematic location and opened #1362
2024-04-12 02:23:31 +02:00
1316ee2c7f Scheduler-test: adjust contention mitigation as result of testing
Investigate the behaviour over a wider range of job loads,
job count and worker pool sizes. Seemingly the processing
can not fully utilise the available worker pool capacity.

By inspection of trace-dumps, one impeding mechanism could
be identified: the »stickiness« of the contention mitigation.
Whenever a worker encounters repeated contention, it steps up
and adds more and more wait cycles to remove pressure from the
schedule coordination. As such this is fine and prevents further
degradation of performance by repeated atomic synchronisation.
However, this throttling was kept up needlessly after further
successful work-pulls. Since job times of several milliseconds
can be expected on average in media processing, such a long
retention would spread a performance degradation over a duration
of several frames. Thus, the scheme for step-down was changed
to decrease the throttling by a power series rather than just
documenting the level.
2024-04-12 02:23:31 +02:00
a6a9155cd9 Scheduler-test: measurements documented 2024-04-09 17:10:21 +02:00
6e7f9edf43 Scheduler-test: calculate linear model as test result
Use the statistic functions imported recently from Yoshimi-test
to compute a linear regression model as immediate test result.

Combining several measurement series, this allows to draw conclusions
about some generic traits and limitations of the scheduler.
2024-04-09 17:10:21 +02:00
3517ab6965 Scheduler-test: fine-tuning of result presentation (Gnuplot)
Visual tweaks specific to this measurement setup
 * include a numeric representation of the regression line
 * include descriptive axis labels
 * improve the key names to clarify their meaning
 * heuristic code for the x-ticks
Package these customisations as a helper function into the measurement tool
2024-04-08 18:45:02 +02:00
8e33194882 Scheduler-test: settle definition of specific test setup and data
After a lot of further tinkering, seemingly arriving at a
somewhat satisfactory solution for the layout and arrangement of
test definitions and especially the table for measurement series.

While the complete setup remains fragile indeed, and complexity is more
hidden than reduced — the pragmatic compromise established yesterday
at least allows to reduce the amount of boilerplate in the test or
measurement setup to make the actual specifics stand out clearly.

----

As an aside, the usage of the `DataFile` type imported from Yoshimi-test
recently was re-shaped more towards a generic handling of tabular data with
CSV storage option; thus renaming the type now into `DataTable`.
Persistent storage is now just one option, while another usage pattern
compounds observation data into table rows, which are then directly
rendered into a CSV string, e.g. for visualisation as Gnuplot graph.
2024-04-08 03:58:15 +02:00
10fa0aaa79 Scheduler-test: design problems impeding clean test-setup
Encountering ''just some design problems related to the test setup,''
which however turn out hard to overcome. Seems that, in my eagerness
to create a succinct and clear presentation of the test, I went into
danger territory, overstretching the abilities of the C++ language.

After working with a set of tools created step by step over an extended span of time,
''for me'' the machinations of this setup seem to be reduced to flipping a toggle
here and there, and I want to focus these active parts while laying out this test.
''This would require'' to create a system of nested scopes, while getting more and more
specific gradually, and moving to the individual case at question; notably any
clarification and definition within those inner focused contexts would have to be
picked up and linked in dynamically.

Yet the C++ language only allows to be ''either'' open and flexible towards
the actual types, or ''alternatively'' to select dynamically within a fixed
set of (virtual) methods, which then must be determined from the beginning.
It is not possible to tweak and adjust base definitions after the fact,
and it is not possible to fill in constant definitions dynamically
with late binding to some specific implementation type provided only
at current scope.

Seems that I am running against that brick wall over and over again,
piling up complexities driven by an desire for succinctness and clarity.

Now attempting to resolve this quite frustrating situation...
- fix the actual type of the TestChainLoad by a typedef in test context
- avoid the definitions (and thus the danger of shadowing)
  and use one `testSetup()` method to place all local adjustments.
2024-04-08 03:54:00 +02:00
d47f24d745 Scheduler-test: reorganise test-setup in Stress-test-rig
With the addition of a second tool `bench::ParameterRange`,
the setup of the test-context for measurement became confusing,
since the original scheme was mostly oriented towards the
''breaking point search.''

On close investigation, I discovered several redundancies, and
moreover, it seems questionable to generate an ''adapted-schedule''
for the Parameter-Range measurement method, which aims at overloading
the scheduler and watch the time to resolve such a load peak.

The solution entertained here is to move most of the schedule-ctx setup
into the base implementation, which is typically just inherited by the
actual testcase setup. This allows to leave the decision whether to build
an adapted schedule to the actual tool. So `bench::BreakingPoint` can
always setup the adapted schedule with a specific stress-factor,
while `bench::ParameterRange` by default does nothing in this
respect, and thus the `ScheduleCtx` will provide a default schedule
with the configured level-duration (and the default for this is
lowered to 200µs here).

In a similar vein, calculation of result data points from the raw measurement
is moved over into the actual test setup, thereby gaining flexibility.
2024-04-08 03:54:00 +02:00
0d3dc91584 Scheduler-test: rework ParameterRange tool for data visualisation
Rework the existing tool to capture the measurement series
into the newly integrated CSV-based data storage, allowing
to turn the results into a Gnuplot-visualisation.
2024-04-04 02:52:57 +02:00
55f8f229f1 Library: customisation of the generated Gnuplot diagram 2024-04-03 19:31:00 +02:00
96202f845a Library: example to schow the secondary diagram
...which is added automatically whenever additional data columns are present

Result can only be verified visually

 * the upper diagram should show the first fibonacci points
 * a (correct) linear regression line should be overlayed in red
 * below, a secondary diagram should appear, with aligned axis
 * the row "one" in this diagram should be shown as impulses
 * the further rows "two" and "three" should be drawn as
   green points, using the secondary Y-axis (values 100-250)
 * Gnuplot can handle missing data points
2024-04-03 00:29:27 +02:00
c997fc2341 Library: develop Gnuplot code for flexible scatter-regression
The idea is to build the Layout-branching into the generated Gnuplot script,
based on the number of data columns detected. If there is at least one further
data column, then the "mulitplot" layout will be used to feature this
additional data in a secondary diagram below with aligned axis;
if more than one additional data column is present, all further
visualisation will draw points, using the secondary Y-axis

Moreover, Gnuplot can calculate the linear regresssion line itself,
and the drawing will then be done using an `arrow` command,
defining a function regLine(x) based on the linear model.
2024-04-02 23:59:59 +02:00
f37e651b61 Library: add some mutual integration between DataFile and CSVData
...both are related to CSV, and it is conceivable
to create inline CSVData in a test case to populate a DataFile
2024-04-02 21:18:23 +02:00
03c2191649 Library: rearrange support for CSV notation
- `forElse` belongs to the metaprogramming utils

- have a CSVLine, which is a string with custom appending mechanism

- this in turn allows CSVData to accept arbitrary sized tuples,
  by rendering them into CSVLine
2024-04-01 22:33:55 +02:00
fc084c1ca5 Library: find a better organisation of entrance points to plotting
The intention is to create a library of convenient building blocks;
providing a visualisation should be as simple as invoking a free function
with CSV data, yet with the ability to tweak some lables or display
variations if desired.

This can be achieved by..
 * having a series of ready-made standard visualisations
 * expose a function call for each, accepting a data-context builder
 * provide secondary convenience shortcuts, which add some of the expected bindings
 * notably a shortcut is provided to take the data as CSV-string
 * augmented by a wrapper/builder to allow defining data points inline
2024-03-31 19:12:43 +02:00
a6084bd2d6 Library: implement generation of a simple data visualisation
CSV data -> Gnuplot script
2024-03-31 01:46:12 +01:00
db0838ddcc Library: draft invocation framework for generating a Gnuplot
Deliberately keep it unstructured and add dedicated functions
for each new emerging use case; hopefully some commen usage scheme
will emerge over time.

 * Data is to be handed in as an iterator over CSV-strings.
 * will have to find out about additional parametrisation on a case-by-case base
2024-03-31 01:45:23 +01:00
f627d42b6e Library: develop a suitable gnuplot layout and styling
The default visuals of gnuplot are simple,
yet tend to look cluttered and are not well suited for our purpose

We need the following presentation
 * a scatter diagram with a regression line
 * additionally a secondary diagram stacked below, with aligned axis

Thus 🠲 R-T-F-M
 * The [http://gnuplot.info/ Gnuplot docu] is exhaustive, yet hard to get into
 * Helpful was this collection of [http://gnuplotting.org/ example solutions for scientific plots]
 * and — Stackoverflow...
2024-03-30 04:08:45 +01:00
918f96bb6f Library: complete ETD data-source binding and test (closes #1359)
A minimalist `TextTemplate` engine is available for in-project use.

 * supports only the bare minimum of features (no programming language)
   * substitution of `${placeholder}` by key-name data access
   * conditional section `${if key}...${end if}`
   * iteration over a data sequence
 * other then most solutions available as library,
   this implementation does **not require** a specific data type,
   nor does it invent a dynamic object system or JSON backend;
   rather, a generic ''Data Source Adapter'' is used, which can
   be specialised to access any kind of ''structured data''
 * the following `DataSource` specialisations are provided
   * `std::map<string,string>`
   * Lumiera »External Tree Description« (based on `GenNode`)
   * a string-based spec for testing
2024-03-28 03:18:02 +01:00
4c4ae0691c Library: verify DataSrc binding for Map 2024-03-28 03:14:21 +01:00
597d8191c7 Library: code the DataSource template
...turns out challenging, since our intention here
is borderline to the intended design of the Lumiera ETD.
It ''should work'' though, when combined with a Variant-visitor...
2024-03-27 04:07:55 +01:00
64f60356b7 Library: prepare for a ETD binding
Document existing data binding logic and investigate in detail
what must be done to enable a similar binding backed by Lumiera's ETD structures.
This analysis highlights some tricky aspects, which can be accommodated by
slight adjustments and generalisations in the `TextTemplate` implementation
 * `GenNode` is not structured string data, rather binary data
 * thus exposing a std::string_view is not adequate, requiring to
   pick up the result type from the actual data binding
 * moreover, to allow for arbitrary nested scopes, a back-pointer
   to the parent scope must be maintained, which requires stable memory locations.
   This can best be solved within the InstanceCore itself, which manages
   the actual hierarchy of data source references.
 * the existing code happens already to fulfil this requirement, but
   for sake of clarity, handling of such a nested scope is now extracted
   into a dedicated operation, to highlight the guaranteed memory layout.
2024-03-27 01:24:41 +01:00
c0439b265c Library: verify proper working of logic constructs
uncovers some minor implementation bugs, as can be expected...
2024-03-26 06:30:23 +01:00
4e26d655a8 Library: workaround for tricky problems with Template-Argument-Deduction
We use a DataSrc<DAT> template to access the actual data to be substituted.
However, when applying the Text-Template, we need to pick the right
specialisation, based on the type of the actual data provided.

Here we face several challenges:

 * Class-Template-Argument-Deduction starts from the *primary* template's constructors.
   Without that, the compiler will only try the copy constructor and will
   never see the constructors of partial specialisations.
   This can be fixed by providing a ''dummy constructor''.
 * The specifics of how to provide a custom CTAD deduction guide
   for a **nested template** are not well documented. I have found
   several bug reports, and seemingly one of these bugs failed my
   my various attempts. Moreover it is ''not clear if such a deduction
   guide can even be given outside of the class definition scope.''
   For the intended usage pattern this would be crucial, since users
   are expected to provide further specialisations of the DataSrc-template
 * Thus I resorted to the ''old school solution,'' which is to use
   a ''free builder function'' as an extension point. Thus users could
   provide further overloads for the `buildDataSrc()` function.
 * Unfortunately, SFINAE-Tricks are way more limited for function overload.
   Thus it seems impossible to have a generic and more specialised cases,
   unless all special cases are disjoint.

Thus the solution is far from perfect, ''yet for the current situation it seems
sufficient'' (and C++20 Concepts will greatly help to resolve this kind of problems)
2024-03-26 02:41:42 +01:00
a89e272e35 Library: supply a string-spec-binding for tests
...implemented by simply parsing the string into key=value pairs,
which are then stored into a shared map. The actual data binding
implementation can thus be inherited from the existing Map-binding
2024-03-25 18:26:17 +01:00
9b6fc3ebe5 Library: fix handling of escapes
While they were detected just fine, thy were passed-through
unaltered, which subverts the purpose of such an escape,
which is to allow for the tag syntax to be present in the
processed, substituted document (e.g. when generating a
shell script)

thus `\${escaped}` becomes `${escaped}`
2024-03-25 15:44:48 +01:00
c09f44e20f Library: complete implementation of Map-databinding
...using a ''special protocol'' to represent iterative data sequences
 * use an Index-Key with a CSV list of element prefixes
 * synthesise key-prefixes for each data element
 * perform lookup with the decorated key first

This allows to somehow ''emulate'' nested associations within a single, flat Map.
Obviously this is more like a proof-of-concept; actually the Map-databinding
is meant to handle the simple cases, where just placeholders are to be substituted.

The logic structures are much more relevant when binding to structural data,
most notably to the Lumiera _External Tree Description_ format, which is
used for model data and inter-layer communication.
2024-03-25 04:23:59 +01:00
58ffbac7f3 Library: implement logic-interpretation
- the basic interpretation of Action-tokens is already in place
 - add the interpretation of conditional and looping constructs
 - this includes helpers for
   * reset to another Action-token index
   * recursive interpretation of the next token
   * handling of nested loop evaluation context

In order to make this implementation compile, also the skeleton
of the Map-string-string data binding must be completed, including
a draft how to handle nested keys in a simple map
2024-03-25 03:30:16 +01:00
dd67b9f97b Library: cover some definition errors 2024-03-25 00:38:35 +01:00
20f2b1b90a Library: complete implementation of code generation
...including the handling of cross jumps / links
...verified by one elaborate example in the tests
2024-03-24 21:42:38 +01:00
aab446ce48 Library: further straighten iteration logic
playing the »fence post problem« the other way round
and abandoning the ''pull processing'' in favour of direct manipulation
leads to much clearer formulation of the code-generation logic
2024-03-24 15:51:38 +01:00
bc8e947f3c Library: remould compiler to active iteration
...turns out the ''pipeline design'' is not a good fit for the
Action compilation, since the compiler needs to refer to previous Actions;
better to let the compiler ''build'' the `ActionSeq`
2024-03-24 14:21:44 +01:00
b835d6a012 Library: get the template compiler basically operative
...implementation of bracketed constructs and cross references still omitted

...define a fairly elaborate test example for parsing
2024-03-24 00:48:04 +01:00
a9cbe7eb90 Library: define skeleton of TextTemplate compilation
...implemented as »custom processing layer« within a
demand-driven parsing pipeline, with the ability to
inject additional Action-tokens to represent the intermittent
constant text between tags; special handling to expose one
constant postfix after the last active tag.
2024-03-23 19:38:53 +01:00
5b53b53c4c Library: solution for ''trailing prefix'' in parser-context
* use a string-view embedded into the context-λ
 * on each match clip off some starting prefix from this string-view
2024-03-23 02:55:28 +01:00
c2df391f48 Library: draft how a pipelined-parser could work 2024-03-23 02:55:28 +01:00
2a60f77bdf Library: improve formulation of the parsing regexp
- allow additional leading and trailing whitespace within token
- more precise on the sequence of keywords
- clearer build-up of the regexp syntax
2024-03-23 02:55:28 +01:00
10bda3a400 Library: develop a token-parsing regular expression
oh my!
2024-03-23 02:55:28 +01:00
a2749adbc9 Library: also cover the smart-ptr usage variant
The way I've written this helper template, as a byproduct
it is also possible to maintain the back-refrence to the container
through a smart-ptr. In this case, the iterator-handle also manages
the ownership automatically.
2024-03-21 19:57:34 +01:00
f716fb0bee Library: build a helper to encapsulate container access by index
...mostly we want the usual convenient handling pattern for iterators,
but with the proviso actually to perform an access by subscript,
and the ability to re-set to another current index
2024-03-21 19:57:34 +01:00
76bd9ba6ce Library: establish evaluation and iteration on application 2024-03-21 19:57:34 +01:00
7893cc99c3 Library: further work out the framework for TextTemplate instantiation 2024-03-21 19:57:34 +01:00
d2dcf6c163 Library: draft the interpretation of a compiled TextTemplate
will use an iteration-pipeline, based on the »State Core« concept
2024-03-21 19:57:34 +01:00
5881b014fe Library: work out a treatment for text template substitution (see: #1359)
* establish the feature set to provide
 * choose scheme for runtime representation
 * break down analysis to individual parsing and execution steps
 * conclude which actions to conduct and the necessary data
 * derive the abstract binding API required
2024-03-21 19:57:34 +01:00
af1f549190 Library: Assessment and plan for a text templating engine
Conducted an extended investigation regarding text templating
and the library solutions available and still maintained today.

The conclusion is
 * there are some mature and widely used solutions available for C++
 * all of these are considered a mismatch for the task at hand,
   which is to generate Gnuplot scripts for test data visualisation

Points of contention
 * all solutions offer a massive feature set, oriented towards web content generation
 * all solutions provide their own structured data type or custom property-tree framework

**Decision** 🠲  better to write a minimalistic templating engine from scratch rather
2024-03-21 19:57:34 +01:00
d3fda114f8 Library: Research -- Gnuplot
Read the documentation and find out how to generate the kind of diagram
necessary for visualisation of Scheduler-Stress-Test observations.

I used to have basic Gnuplot knowledge, and thus had to find out about
- reading CSV
- supported diagram types
- layering and styling

Conclusion: will use Gnuplot and generate a script from Test code
2024-03-21 19:57:34 +01:00
aa93bf9285 Library: cover statistic functions and linear regression 2024-03-16 03:05:49 +01:00
599407deea Library: complete coverage of CSV data table including storage
also encompasses some coverage for the simplistic CSV format
implemented as storage backend for this data table
2024-03-15 02:45:45 +01:00
3b3600379a Library: introduce formating variants for decimal10
showDecimal -> decimal10 (maximal precision to survive round-trip through decimal representation=

showComplete -> max_decimal10 (enough decimal places to capture each possible distinct floating-point value)


Use these new functions to rewrite the format4csv() helper
2024-03-14 17:32:22 +01:00
4a8364e422 Library: extend the DataFile to allow using it without storage
...seems obvious and does not compromise the simplistic design...
...we do check the file path anyway, just need to add saveAs()...
2024-03-13 18:57:48 +01:00
1c2cbd4d47 Library: coverage for some filesystem convenience helpers
...created as a byproduct of the TempDir feature,
which in turn is required to do ''any'' meaningful unit-test
of filesystem related functionality.
2024-03-13 03:52:35 +01:00
a6aad5261c Library: complete and verify temp-dir helper
verify also that clean-up happens in case of exceptions thrown;
as an aside, add Macro to check for ''any'' exception and match
on something in the message (as opposed to just a Lumiera Exception)
2024-03-13 02:50:13 +01:00
18b1d37a3d Library: also create unique temporary files
...using the same method for sake of uniformity

Also move the permissions helpers to the file.hpp support functions
and setup a separate unit test for these
2024-03-12 22:54:05 +01:00
a1832b1cb9 Library: base implementation of temp-dir creation
Inspired by https://stackoverflow.com/a/58454949

Verified behaviour of fs::create_directory
 --> it returns true only if it ''indeed could create'' a new directory
 --> it returns false if the directory exists already
 --> it throws when some other obstacle shows up

As an aside: the Header include/limits.h could be cleaned up,
and it is used solely from C++ code, thus could be typed, namespaced etc.
2024-03-12 20:14:29 +01:00
b426ea4921 Library: simple default implementation for random sequences
Since this is a much more complicated topic,
for now I decided to establish two instances through global variables:
 * a sequence seeded with a fixed starting value
 * another sequence seeded from a true entropy source

What we actually need however is some kind of execution framework
to define points of random-seeding and to capture seed values for
reproducible tests.
2024-03-12 02:34:19 +01:00
7a3e4098c8 Library: some first thoughts regarding random number generation
Relying on random numbers for verification and measurements is known to be problematic.
At some point we are bound to control the seed values -- and in the actual
application usage we want to record sequence seeding in the event log.

Some initial thoughts regarding this intricate topic.
 * a low-ceremony drop-in replacement for rand() is required
 * we want the ability to pick-up and control each and every usage eventually
 * however, some usages explicitly require true randomness
 * the ability to use separate streams of random-number generation is desirable
2024-03-12 00:48:11 +01:00
6e8c07ccd6 Library: draft tests to document the new features
Yesterday I decided to include some facilities I have written in 2022
for the Yoshimi-Testsuite. The intention is to use these as-is, and just
to adapt them stylistically to the Lumiera code base.

However — at least some basic documentation in the form of
very basic unit-tests can be considered »acceptance criteria«
2024-03-11 17:44:19 +01:00
0e88dec28a Library: integrate into the Lumiera code base
- reformat in Lumieara-GNU style
- use the Lumiera exceptions
- use Lumiera format-string frontend
- use lib/util

NOTE: I am the original author of the code introduced here,
and thus I can re-license it under GPL 2+
2024-03-11 17:38:30 +01:00
a983a506b0 Scheduler-test: simplify graph generation yet more
Initially the model was that of a single graph starting
with one seed node and joining all chains into a single exit node.

This however is not well suited to simulate realistic calculations,
and thus the ability for injecting additional seeds and to randomly
sever some chains was added -- which overthrows the assumption of
a single exit node at the end, where the final hash can be retrieved.

The topology generation used to pick up all open ends, in order to
join them explicitly into a reserved last node; in the light of the
above changes, this seems like an superfluous complexity, and adds
a lot of redundant checks to the code, since the main body of the
algorithm, in its current form, already does all the necessary
bound checks. It suffices thus to just terminate the processing
when the complete node space is visited and wired.

Unfortunately this requires to fix basically all node hashes
and a lot of the statistics values of the test; yet overall
the generated graphs are much more logical; so this change
is deemed worth the effort.
2024-03-10 02:47:32 +01:00
d8eb334b17 Scheduler-test: preconfigured graph with unconnected nodes
Allow easily to generate a Chain-Load with all nodes unconnected,
yet each node on a separate level.

Fix a deficiency in the graph generation, which caused spurious
connections to be added at the last node, since the prune rule
was not checked
2024-03-09 18:06:08 +01:00
ab5900c82e Scheduler-test: fix error with topology
...the previous setup produced a single linear chain
instead of a set of unconnected nodes.

With this, the behaviour is more like expected,
but concurrency is still too low
2024-03-08 18:16:18 +01:00
605d747b8d Scheduler-test: attempt to find a viable Scheduler setup for this measurement
- better use a Test-Chain-Load without any dependencies
- schedule all at once
- employ instrumentation
- use the inner »overall time« as dependent result variable

The timing results now show an almost perfect linear dependency.
Also the inner overall time seems to omit the setup and tear-down time.
But other observed values (notably the avgConcurrency) do not line up
2024-03-08 01:30:12 +01:00
2556151304 Scheduler-test: simple implementation of range coverage
- fill the range randomly with probe points
- use the node count as independent parameter
- measurement method *works as intended*
- results indeed show a linear relationship

Results are ''interesting'' however, since the (par,time) points
seem to be arranged into two lines, implying that about half
of the runs were somehow ''degraded'' and performed way slower.
2024-02-24 04:17:05 +01:00
a117e6e8c5 Scheduler-test: consider using a complementary measurement method
With the latest improvements, the »breaking point search« works as expected
and yields meaningful data; however — it seems to be well suited rather
for specific setups, which involve an extended graph with massive dependencies,
because only such a setup produces a clearly defined ''breaking point.''

Thus I'm considering to complement this research by another measurement setup
to establish a linear regression model of the Scheduler expense.

To allow integration of this different setup into the existing stress-test-rig,
some rearrangements of the builder notation are necessary; especially we need
to pass the type name of the actual tool, and it seems indicated to
reorder the source code to provide the config base class `StressRig`
at the top, followed by a long (and very technical) implementation
namespace.
2024-02-23 17:29:50 +01:00
93729e5667 Scheduler-test: more precise accounting for expected concurrency
It turns out to be not correct using all the divergence in concurrency
as a form factor, since it is quite common that not all cores can be active
at every level, given the structural constraints as dictated by the load graph.

On the other hand, if the empirical work (non wait-time) concurrency
systematically differs from the simple model used for establishing the schedule,
then this should indeed be considered a form factor and deduced from
the effective stress factor, since it is not a reserve available for speed-up

The solution entertained here is to derive an effective compounded sum
of weights from the calculation used to build the schedule. This compounded
weight sum is typically lower than the plain sum of all node weights, which
is precisely due to the theoretical amount of expense reduction assumed
in the schedule generation. So this gives us a handle at the theoretically
expected expense and through the plain weight sum, we may draw conclusion
about the effective concurrency expected in this schedule.

Taking only this part as base for the empirical deviations yields search results
very close to stressFactor ~1 -- implying that the test setup now
observes what was intended to observe...
2024-02-23 02:02:20 +01:00
2d1bd2b765 Scheduler-test: fix deficiencies in search control mechanism
In binary search, in order to establish the invariant initially,
a loop is necessary, since a single step might not be sufficient.

Moreover, the ongoing adjustments jeopardise detection of the
statistical breaking point condition, by causing a negative delta
due to gradually approaching the point of convergence -- leading
to an ongoing search in a region beyond the actual breaking point.
2024-02-19 17:38:04 +01:00
ff39aed7ea Scheduler-test: fix feedback adjustments for breaking point search
Various misconceptions identified in the feedback path of the test algorithm.
- statistics are cumulative, which must be incorporated by norming on time base
- average concurrency includes idle times, which is besides the point within this
  test setup, since additional wait-phases are injected when reducing stress
2024-02-19 17:38:04 +01:00
96df8b20f9 Scheduler-test: introduce a form-factor to account for empiric adaptation
Relying on the new instrumentation facility, the actually effective
concurrency and cumulative run time of the test jobs can be established.
These can now be cast into a form-factor to represent actual excess expenses
in relation to the theoretical model.

By allowing to adjust the adapted schedule by this form factor,
it can be made to reflect more closely the actual empiric load,
hopefully leading to a more realistic effect of the stress-factor
and thus results better suited to conclude on generic behaviour.
2024-02-18 18:01:21 +01:00
7efaf5f0cc Scheduler-test: document new instrumentation facility with simple test
...turns out rather challenging to come up with any test case,
that is both meaningful, simple to setup and understand, yet still
produces somewhat stable values. `IncidenceCount` seems most valuable
for investigation and direct inspection of results
2024-02-17 21:55:21 +01:00
0e7bdcc5b5 Scheduler-test: experiment with extended load and run time
Various experiments to watch Scheduler behaviour under extended load.
Notably the example committed here makes the Scheduler run for 1.2 sec
and process 800 jobs with 10ms each, thereby putting the system into
100% load on all CPUs
2024-02-16 03:45:27 +01:00
27b34c4ed6 Scheduler-test: complements and fixes for the instrumentation
- supplement the pre-dimensioning for data capture; without that,
  sporadic memory corruption indeed happens (as expected, since
  concurrent re-allocation of the vector with an entry for each
  thread is not threadsafe, and this test shows much contention)

- add a top-level logging for better diagnostics of errors
  emanating from the test run
2024-02-15 20:33:28 +01:00
3e1239bd71 Scheduler-test: integrate instrumentation as optional feature
...can be activated on the Test-Chain-Load
...add a test case to validate its operation
2024-02-15 02:43:44 +01:00
580c1f1f68 Scheduler-test: complete instrumentation helper
Verify proper operation under pressure
using a multithreaded stress test
2024-02-15 00:52:59 +01:00
d0c1017580 Scheduler-test: resolve inconsistency in time accounting for instrumentation
Basically users are free to place the measurement calls to their liking.
This implies that bracketed measurement intervals can be defined overlapping
even within a single thread, thereby accounting the overlapping time interval
several times. However, for the time spent per thread, only actual thread
activity should be counted, disregarding overlaps. Thus introduce a
new aggregate, ''active time'', which is the sum of all thread times.

As an aside, do not need explicit randomness for the simple two-thread
test case — timings are random anyway...

+ bugfix for out-of-bounds access
2024-02-14 19:59:14 +01:00
9f0878f885 Scheduler-test: implement accounting for concurrency for instrumentation
...since we've established already an integration over the event timeline,
it is just one simple further step to determine the concurrency level
on each individual segment of the timeline. Based on this attribution

- the averaged concurrenty within the observation range can be computed as weighted mean
- moreover we can account for the precise cumulated time spent at each concurrency level
2024-02-14 04:18:43 +01:00
a1abed68f4 Scheduler-test: implement differentiated statistics counting for instrumentation
...break down the integration of the activation count over time
   into individual accounting
   - for each caseID
   - for each thread
2024-02-13 02:25:52 +01:00
08847ae283 Scheduler-test: implement the simplest case for the instrumentation
...which is to account for the cumulative time spent in code
marked by bracketed measurement calls ("enter" ... "leave")
2024-02-12 21:43:57 +01:00
754b3a2ea6 Scheduler-test: define storage for instrumentation helper
...using a simplistic allocation of next-slot based on initialisation
of a thread_local storage. This implies that this helper can not be
reset or reused, and that there can not be multiple or long-lived instances.

Keep-it-simple for now...
2024-02-12 20:26:38 +01:00
a68adb0364 Scheduler-test: need some instrumentation helper
...to sort out the interpretation of measurement results,
the actual duration and concurrency of ComputationLoad invocations
should be recorded, allowing to draw conclusions regarding the
Scheduler's performance as opposed to further system and thread
management effects due to concurrent operation under pressure.
2024-02-12 18:01:43 +01:00
54a91bcd5a Scheduler-test: investigation...
...and reflection about goals, methods of measurement and possible interpretation
2024-02-11 17:38:20 +01:00
602b7dbe3a Scheduler-test: continue investigation - combine base expense with stress factor
After an extended break due to "real life issues"....
Pick up the investigation, with the goal to ascertain a valid definition
and understanding of all test parameters. A first step is to establish
a baseline ''without using a computational load''; this might be some kind
of base overhead of the scheduler.

However -- the way the test scaffolding was built, it is difficult to
create a feedback loop for the statistical test setup with binary search,
since it is not really clear how the single control parameter of the test algorithm,
the so called "stress factor", shall be interpreted and how it can be
combined with a base load.

An extended series of tests, while watching the observed value patterns qualitatively,
seems to corroborate the former results, indicating that the base expense
in my test setup (using a debug build) is at ~200µs / Node / core.

Yet the difficulty to interpret this result and arrive at a logical and generic model
prevents me from translating this into a measurement scheme, which can
be executed independently from a specific test setup and hardware
2024-02-11 03:53:42 +01:00
0aa1edf07c Scheduler-test: investigate behaviour of a load for stress testing
The goal is to devise a load more akin to the expected real-world processing patterns,
and then to increase the density to establish a breaking point.

Preliminary investigations focus on establishing the properties of this load
and to ensure the actual computation load behaves as expected.

Using the third Graph pattern prepared thus far, which produces
short chains of length=2, yet immediately spread out to maximum concurrency.
This leads to 5.8 Nodes / Level on average.
2024-02-10 18:58:41 +01:00
6a08c97543 Scheduler-test: fix Segfault in test setup
...as it turned out, this segfault was caused by flaws in the ScheduleCtx
used for generate the test-schedule; especially when all node-spreads are set
to zero and thus all jobs are scheduled immediately at t=0, there was a loophole
in the logic to set the dependencies for the final »wake-up« job.

When running such a schedule in the Stress-Test-Bench, the next measurement run
could be started due to a premature wake-up job, thereby overrunning the previous
test-run, which could be still in the middle of computations.

So this was not a bug in the Scheduler itself, yet something to take care of
later when programming the actual Job-Planning and schedule generation.
2024-01-11 23:11:21 +01:00
81d4b5d323 Scheduler-test: setup in Stress-Test-Rig
...use the scheme established thus far
2024-01-10 20:39:20 +01:00
3674d82bdf Scheduler-test: next goal -- massively parallel work jobs
Search processing pattern for massive parallel test.
The goal is to get all cores into active processing most of the time,
thus we need a graph with low dependency management overhead, which is
also consistently wide horizontally to have several jobs in working state
all of the time. The investigation aims at finding out about systematic
overheads in such a setup.
2024-01-10 20:34:09 +01:00
3fb4baefd5 Scheduler-test: optionally allow to propagate immediately
This is just another (obvious) degree of freedom, which could be
interesting to explore in stress testing, while probably not of much
relevance in practice (if a job is expected to become runable earlier,
in can as well be just scheduled earlier).

Some experimentation shows that the timing measurements exhibit more
fluctuations, but also slightly better times when pressure is low, which
is pretty much what I'd expect. When raising pressure, the average
times converge towards the same time range as observed with time bound
propagation.

Note that enabling this variation requires to wire a boolean switch
over various layers of abstraction; arguably this is an unnecessary
complexity and could be retracted once the »experimentation phase«
is over.

This completes the preparation of a Scheduler Stress-Test setup.
2024-01-09 02:29:35 +01:00
0065dabed1 Scheduler-test: investigate volatile in computation-load
The `volatile` was used asymmetrically and there was concern that
this usage makes the `ComutationalLoad` dependent on concurrency.
However, an impact could not be confirmed empirically.

Moreover, to simplify this kind of tests, make the `schedDepends`
directly configurable in the Stress-Test-Rig.
2024-01-08 03:38:34 +01:00
f37b67f9bb Scheduler-test: ability to propagate solely by NOTIFY
...watching those dumps on the example Graph with excessive dependencies
made blatantly clear that we're dispatching a lot of unnecessary jobs,
since the actual continuation is /always/ triggered by the dependency-NOTIFY.
Before the rework of NOTIFY-Handling, this was rather obscured, but now,
since the NOTIFY trigger itself is also dispatched by the Scheduler,
it ''must be this job'' which actually continues the calculation, since
the main job ''can not pass the gate'' before the dependency notification
arrives.

Thus I've now added a variation to the test setup where all these duplicate
jobs are simply omitted. And, as expected, the computation runs faster
and with less signs of contention. Together with the other additional
parameter (the base expense) we might now actually be able to narrow down
on the observation of a ''expense socket'', which can then be
attributed to something like an ''inherent scheduler overhead''
2024-01-06 03:45:55 +01:00
001aa829c5 Scheduler-test: implement base offset per node
...actually difficult to integrate into the existing scheme,
which is entirely level-based. Can only be added to the individual Jobs,
not to the planning and completion-jobs — which actually shouldn't be a problem,
since it is beneficial to dispatch the planning runs earlier
2024-01-06 02:43:06 +01:00
54f238c510 Scheduler-test: further configuration options for flexible testing
The next goal is to determine basic performance characteristics
of the Scheduler implementation written thus far;
to help with these investigations some added flexibility seems expedient

- the ability to define a per-job base expense
- added flexibility regarding the scheduling of dependencies

This changeset introduces configuration options
2024-01-06 01:32:14 +01:00
032e4f6db5 Scheduler-test: extract search algo into lib 2024-01-06 01:23:00 +01:00
e52aed0b3c Scheduler-test: simplify binary search implementation
While the idea with capturing observation values is nice,
it definitively does not belong into a library impl of the
search algorithm, because this is usage specific and grossly
complicates the invocation.

Rather, observation data can be captured by side-effect
from the probe-λ holding the actual measurement run.
2024-01-04 02:37:05 +01:00
bf1eac02dd Scheduler-test: binary search over continuous domain
- textbook implementation
- capture results from visited points
- average results form the last three points to damp statistic fluctuations
2024-01-04 00:04:22 +01:00
54e489b9b6 Scheduler-test: define criterion for breaking point
This statistical criterion defines when to count observed Scheduler performance
as loosing control. The test is comprised of three observations, which
all must be confirmed:

- an individual run counts as accidentally failed when the execution slips
  away by more than 2ms with respect to the defined overall schedule.
  When more than 55% of all observed runs are considered as failed,
  the first condition is met
- moreover, the observed standard derivation must also surpass the
  same limit of > 2ms, which indicates that the Scheduling mechanism
  is under substantial strain (on average, the slip is ~ 200µs)
- the third condition is that the ''averaged delta'' has surpassed
  4ms, which is 2 times the basic failure indicator.

These conditions are based on watching the Scheduler in operation;
typically all three conditions slip away by large margin after a
very narrow yet critical increase in the stress level.

Using three conditions together should improve robustness; often
the problems to keep up with the schedule build up over some parameter
range, yet the actual decision should be based on complete loss of control.
2024-01-03 21:11:20 +01:00
fda34a42ca Scheduler-test: incorporate statistics computation
adapt the code written yesterday explicitly for the test case
into the new framework for performing a stress-test run.
Notable difference: times converted to millisecond immediately
2024-01-03 16:27:07 +01:00
e704f4aae0 Scheduler-test: build configurable measurement setup
Elaborate the draft to include all the elements used directly in the test case thus far;
the goal is to introduce some structuring and leave room for flexible confguration,
while implementing the actual binary search as library function over Lambdas.

My expectation is to write a series of individual test instances with varying parameters;
while it seems possible to add further performance test variations into that scheme later on.
2024-01-03 02:18:15 +01:00
6f4bd150fd Scheduler-test: draft a structure to formalise these investigations
- the goal is to run a binary search
- the search condition should be factored out
- thus some kind of framework or DSL is required,
  to separate the technicalities of the measurement
  from the specifics of the actual test case.
2024-01-03 00:45:17 +01:00
29699991a0 Scheduler-test: watch statistics with increasing stress
- repeated invocations of the same test setup for statistics
- the usual nasty 64-node graph with massive fork out
- limit concurrency to 4 cores
- tabulate data to look for clues regarding a trigger criteria

Hypothesis: The Scheduler slips off schedule when all of the
following three criteria are met:
- more than 55% glitches with Δ > 2ms
- σ > 2ms
- ∅Δ > 4ms
2024-01-02 18:44:20 +01:00
0c9485294e Scheduler-test: experiment with varying stress levels 2024-01-02 15:45:40 +01:00
bb2bbc0e02 Scheduler-test: verify adapted schedule with stress-factor
- schedule can now be adapted to concurrency and expected distribution of runtimes
- additional stress factor to press the schedule (1.0 is nominal speed)
- observed run-time now without Scheduler start-up and pre-roll
- document and verify computed numbers
2024-01-02 00:24:28 +01:00
813f8721f7 Scheduler-test: build adapted schedule
...based on the adapted time-factor sequence
implemented yesterday in TestChainLoad itself

- in this case, the TimeBase from the computation load is used as level speed
- this »base beat« is then modulated by the timing factor sequence
- working in an additional stress factor to press the schedule uniformly
- actual start time will be added as offset once the actual test commences
2024-01-01 22:48:27 +01:00
f4dd309476 Scheduler-test: change test setup to use a schedule table
...up to now, we've relied on a regular schedule governed solely
by the progression of node levels, with a fixed level speed
defaulting to 1ms per level.

But in preparation of stress tesging, we need a schedule adapted
to the expected distribution of computation times, otherwise
we'll not be able to factor out the actual computation graph
connectivity. The goal is to establish a distinctive
**breaking point** when the scheduler is unable to cope with
the provided schedule.
2024-01-01 21:07:16 +01:00
55cb028abf Scheduler-test: document and verify weight adapted timing
The helper developed thus far produces a sequence of
weight factors per level, which could then be multiplied
with an actual delay base time to produce a concrete schedule.

These calculations, while simple, are difficult to understand;
recommended to use the values tabulated in this test together
with a `graphviz` rendering of the node graph (🠲 `printTopologyDOT()`)
2023-12-31 21:59:41 +01:00
e9e7d954b1 Scheduler-test: formula to guess expense
The intention is to establish a theoretical limit for the expense,
given some degree of concurrency. In reality, the expense should always
be greater, since the time is not just split by the number of cores;
rather we need to chain up existing jobs of various weight on the available
cores (which is a special case of the box packing problem).

With this formula, an ideal weight factor can be determined for each level,
and then summing up the sequence of levels gives us a guess for a sensible
timing for the overall scheduler
2023-12-31 03:14:59 +01:00
409a60238a Scheduler-test: extract a generic grouping iterator
...so IterExplorer got yet another processing layer,
which uses the grouping mechanics developed yesterday,
but is freely configurable through λ-Functions.

At actual usage sit in TestChainLoad, now only the actual
aggregation computation must be supplied, and follow-up computations
can now be chained up easily as further transformation layers.
2023-12-31 00:41:01 +01:00
fec117039e Scheduler-test: need this group aggregation as pipeline rather
Yesterday I've written a simple loop-based implementation of
a grouping aggregation to count the node weights per level.

Unfortunately it turns out we'll use several flavours of this
and we'd have to chain up postprocessing -- thus from a usage perspective
it would be better to have the same functionality packaged as interator pipeline.
This turns out to be surprisingly tricky and there is no suitable library
function available, which means I'll have to write one myself.

This changeset is the first step into this direction: reformulate
the simple for-loop into a demand-driven grouping iterator
2023-12-30 02:15:38 +01:00
f04035a030 Scheduler-test: draft calculation of level-weight based schedule
...the idea is to use the sum of node weights per level
to create a schedule, which more closely reflects the distribution
of actual computation time. Hopefully such a schedule can then be
squeezed or stretched by a time factor to find out a ''breaking point'',
at which the Scheduler is no longer able to keep up.
2023-12-29 01:07:26 +01:00
47ae4f237c Scheduler-test: investigate and fix further memory manager problem
In-depth investigation and reasoning highlighted another problem,
which could lead to memory corruption in rare cases; in the end
I found a solution by caching the ''address'' of the current Epoch
and re-validating this address on each Epoch-overflow.

After some difficulties getting any reliable measurement for a Release-build,
it turned out that this solution even ''improves performance by 22%''

Remark-1: the static blockFlow::Config prevents simple measurements by
  just recompiling one translation unit; it is necessary to build the
  relevant parts of Vault-layer with optimisation to get reliable numbers

Remark-2: performing a full non-DEBUG build highlighted two missing
  header-inclusions to allow for the necessary template specialisations.
2023-12-28 02:13:24 +01:00
3716a5b3d4 Scheduler-test: address defects in memory manager
...discovered by during investigation of latest Scheduler failures.
The root of the problems is that block overflow can potentially trigger
expansion of the allocation pool. Under some circumstances, this on-the fly
allocation requires a rotation of index slots, thereby invalidating
existing iterators.

While such behaviour is not uncommon with storage data structures (see std::vector),
in this case it turns out problematic because due to performance considerations,
a usage pattern emerged which exploits re-using existing storage »Slots« with known
deadline. This optimisation seems to have significant leverage on the
planning jobs, which happen to allocated and arrange a whole strike of
Activities with similar deadlines.

One of these problem situations can easily be fixed, since it is triggered
through the iterator itself, using a delegate function to request a storage expansion,
at which point the iterator is able to re-link and fix its internal index.
This solution also has no tangible performance implications in optimised code.

Unfortunately there remains one obscure corner case where such an pool expansion
could also have invalidated other iterators, which are then used later to
attach dependency relations; even a partial fix for that problem seems
to cause considerable performance cost of about -14% in optimised code.
2023-12-27 00:16:03 +01:00
af680cdfd9 Scheduler-test: adapt tests to changed logic at entrance
- now there can not be any direct dispatch anymore when entering events
- thus there is no decision logic at entrance anymore
- rather the work-function implementation moved down into Layer-2
- so add a unit-test like coverage there (integration in SchedulerService_test)
2023-12-27 00:16:03 +01:00
09f0e92ea3 Scheduler-test: reorganise planning-job entrance and coordination
This amounts to a rather massive refactoring, prompted by the enduring problems
observed when pressing the scheduler. All the various glitches and (fixed) crashes
are related to the way how planning-jobs enter the schedule items,
which is also closely tied to the difficulties getting the locking
for planning-jobs correct.

The solution pursued hereby is to reorder the main avenues into the
scheduler implementation. There is now a streamlined main entrance,
which **always** enqueues only, allowing to omit most checks and
coordination. On the other hand, the complete coordination and dispatch
of the work capacity is now shifted down into the SchedulerCommutator,
thereby linking all coordination and access control close together
into a single implementation facility.

If this works out as intended
 - several repeated checks on the Grooming-Token could be omitted (performance)
 - the planning-job would no longer be able to loose / drop the Token,
   thereby running enforcedly single-threaded (as was the original intention)
 - since all planning effectively originates from planning-jobs, this
   would allow to omit many safety barriers and complexities at the
   scheduler entrance avenue, since now all entries just go into the queue.

WIP: tests pass compiler, but must be adapted / reworked
2023-12-26 03:06:30 +01:00
100252acdf Scheduler-test: bugfix - protect further allocation with guard
...whenever the planning falls behind schedule, it can happen that
the planner-worker immediately dispatches its own jobs; while the calculation
is broken anyway in this situation, especially this call scheme leads to
dropping the Grooming-Token prior to the calculation dispatched directly.

Since the dependency relation can only be established after creating
both predecessor and successor schedules, the corresponding allocation
of the NOTIFY-Activity is not protected against concurrent access,
which probably leads to the assertion failure due to corruption of
the allocator's internal data structures...
2023-12-23 21:38:53 +01:00
dedfbf4984 Scheduler-test: investigate planning failure
- fix mistake in schdule time for planning chunks (must use start, not end of chunk)
- allow to configure the heuristics for pre-roll (time reserved for planning a node)
2023-12-23 21:38:51 +01:00
90ab20be61 Scheduler-test: press harder with long and massive graph
...observing multiple failures, which seem to be interconnected

- the test-setup with the planning chunk pre-roll is insufficient

- basically it is not possible to perform further concurrent planning,
  without getting behind the actual schedule; at least in the setup
  with DUMP print statements (which slowdown everything)

- muliple chained re-entrant calls into the planning function can result

- the **ASSERTION in the Allocator** was triggered again

- the log+stacktrace indicate that there **is still a Gap**
  in the logic to protect the allocations via Grooming-Token
2023-12-22 00:33:51 +01:00
2cd51fa714 Scheduler-test: fix out-of-bound access
...causing the system to freeze due to excess memory allocation.

Fortunately it turned out this was not an error in the Scheduler core
or memory manager, but rather a sloppiness in the test scaffolding.
However, this incident highlights that the memory manager lacks some
sanity checks to prevent outright nonsensical allocation requests.

Moreover it became clear again that the allocation happens ''already before''
entering the Scheduler — and thus the existing sanity check comes too late.
Now I've used the same reasoning also for additional checks in the allocator,
limiting the Epoch increment to 3000 and the total memory allocation to 8GiB

Talking of Gibitbytes...
indeed we could use a shorthand notation for that purpose...
2023-12-21 20:25:43 +01:00
cca9787e08 Scheduler-test: increase capacity focus for neartime schedule
The scheduler implementation uses a randomised redistribution of
work capacity, taking into account the current ''scale'' of next pending event.
While this works surprisingly well overall, sometimes, in very tight and dense scheules
the workers seem to be spread somewhat too arbitrarily. Thus, if the scheduler
is working through a zone with several events as close as 1ms, often it takes
up to 3ms for another worker to show up.

With this change, the scattering range in the ''near zone'' (50µs ... 5ms)
is made dynamic, and now flexibly depends on current head time.
The closer the next event, the more tightly focussed will be the
capacity redistribution, if capacity becomes available just some 100µs
ahead of next demand, it is no longer „sent away“, but rather relocated
by roughly the same distance behind the next event.
2023-12-21 20:25:43 +01:00
707fbc2933 Scheduler-test: implement contention mitigation scheme
while my basic assessment is still that contention will not play a significant
role given the expected real world usage scenario — when testing with
tighter schedule and rather short jobs (500µs), some phases of massive contention
can be observed, leading to significant slow-down of the test.

The major problem seems to be that extended phases of contention will
effectively cause several workers to remain in an active spinning-loop for
multiple microseconds, while also permanently reading the atomic lock.

Thus an adaptive scheme is introduced: after some repeated contention events,
workers now throttle down by themselves, with polling delays increased
with exponential stepping up to 2ms. This turns out to be surprisingly
effective and completely removes any observed delays in the test setup.
2023-12-20 20:25:17 +01:00
84b92c2ee3 Scheduler-test: investigate and fix problem with the new Guard
...turns out to be a secondary problem (but must be fixed non the less).
Since the planning-job no longer drops the token now, the workers
have to wait; since they are waiting actively and contending on the token,
a significant slowdown can happen.

Sometimes the planning job gets behind its own scheduler and thus
enters dispatch, in which case it drops the GoomingToken, causing
an Assertion failure on return.

The **actual problem** however is the slowdown due to active spinning
2023-12-20 02:50:47 +01:00
b497980522 Scheduler-test: guard memory allocations by grooming-token
Turns out that we need to implemented fine grained and explicit handling logic
to ensure that Activity planning only ever happens protected by the Grooming-Token.
This is in accordance to the original design, which dictates that all management tasks
must be done in »management mode«, which can only be entered by a single thread at a time.
The underlying assumption is that the effort for management work is dwarfed in comparison
to any media calculation work.

However, in
5c6354882d
...I discovered an insidious border condition, an in an attempt to fix it,
I broke that fundamental assumpton. The problem arises from the fact that we
do want to expose a *public API* of the Scheduler. Even while this is only used
to ''seed'' a calculation stream, because any further planning- and management work
will be performed by the workers themselves (this is a design decision, we do not
employ a "scheduler thread")
Anyway, since the Scheduler API ''is'' public, ''someone from the outside'' could
invoke those functions, and — unaware of any Scheduler internals — will
automatically acquire the Grooming-Token, yet never release it,
leading to deadlock.

So we need a dedicated solution, which is hereby implemented as a
scoped guard: in the standard case, the caller is a management-job and
thus already holds the token (and nothing must be done). But in the
rare case of an »outsider«, this guard now ''transparently'' acquires
the token (possibly with a blocking wait) and ''drops it when leaving scope''
2023-12-19 23:38:57 +01:00
523586570f Scheduler-test: refactor and clarify λ-post
In the course of the last refactorings, a slight change in processing
order was introduced, which turned out to improve parallelisation considerably.

- Some further implementation logic can be relegated into the ActivationEvent
- the handling of start times now also incldues a check for sake of symmetry
- document the semantics change: λ-post no longer dispatches directly
2023-12-19 21:51:33 +01:00
c4807abf8a Scheduler-test: planning for stress-tests 2023-12-19 21:06:23 +01:00
67036f45b0 Scheduler-test: Integration-test now running smoothly
The last round of refactorings yielded significant improvements
 - parallelisation now works as expected
 - processing progresses closer to the schedule
 - run time was reduced

The processing load for this test is tuned in a way to overload the
scheduler massively at the end -- the result must be correct non the less.

There was one notable glitch with an assertion failure from the memory manager.
Hopefully I can reproduce this by pressing and overloading the Scheduler more...
2023-12-18 23:34:10 +01:00
ba82a446fd Scheduler-test: address follow-up problem with depth-first
The rework from yesterday turned out to be effective ... unfortunately
a bit to much: since now late follow-up notifications take precedence,
a single worker tends to process the complete chain depth-first, because
the first chain will be followed and processed, even before the worker
was able to post the tasks for the other branches. Thus this single
worker is the only one to get a chance to proceed.

After some consideration, I am now leaning towards a fundamental change,
instead of just fixing some unfavourable behaviour pattern: while the
language semantics remains the same, the scheduler should no longer
directly dispatch into the next chain **from λ-post**. That is, whenever
a POST / NOTIFY is issued from the Activity-chain, the scheduler goes
through prioritisation.

This has further ramifications: we do not need a self-inhibition mechanism
any more (since now NOTIFY picks up the schedule time of the target).

With these changes, processing seems to proceed more smoothly,
albeit still with lots of contention on the Grooming token,
at least in the example structure tested here.
2023-12-17 23:46:44 +01:00
1cbb6b7371 Scheduler-test: rework handling of notifications in the Activity-Language
While the recent refactoring...
206c67cc

...was a step into the right direction, it pushed too hard,
overlooking the requirement to protect the scheduler contents
and thus all of the Activity-chains against concurrent modification.
Moreover, the recent solution still seems not quite orthogonal.

Thus the handling of notifications was thoroughly reworked:
- the explicit "double-dispatch" was removed, since actual usage
  of the language indicates that we only need notifications to
  Gate (and Hook), but not to any other conceivable Activity.
- thus it seems unnecessary to turn "notification" into some kind
  of secondary work mode. Rather, it is folded as special case
  into the regular dispatch.

This leads to new processing rules:
- a POST goes into λ-post (obviously... that's its meaning)
- a NOTIFY now passes its *target* into λ-post
- λ-post invokes ''dispatch''
- and **dispatching a Gate now implies to notify the Gate**

This greatly simplifies the »state machine« in the Activity-Language,
but also incurs some limitations (which seems adequate, since it is
now clear that we do not ''schedule'' or ''dispatch'' arbitrary
Activities — rather we'll do this only with POST and NOTIFY,
and all further processing happens by passing activation
along the chain, without involving the Scheduler)
2023-12-16 23:47:50 +01:00
75b5eea2d3 Scheduler-test: option to require activation by scheduler
use a feature of the Activity-Language prepared for this purpose:
self-Inhibition of the Chain. This prevents a prerequisite-NOTIFY
to trigger a complete chain of available tasks, before these tasks
have actually reached their nominal scheduling time.

This has the effect to align the computations much more strictly
with the defined schedule
2023-12-14 01:49:46 +01:00
3e84224f74 Scheduler-test: force dependency-wait to wake-up job
The main (test) thread is kept in a blocking wait until the
planned schedule is completed. If however the schedule overruns,
the wake-up job could just be triggered prematurely.

This can easily be prevented by adding a dependency from the last
computation job to the wake-up job. If the computation somehow
flounders, the SAFETY_TIMEOUT (5s) will eventually raise
an exception to let the test fail cleanly (shutting down
the Scheduler automatically)
2023-12-13 22:55:28 +01:00
206c67cc8a Scheduler-test: adapt λ-post to include deadline info
...it seems impossible to solve this conundrum other than by
opening a path to override a contextual deadline setting from
within the core Activity-Language logic.

This will be used in two cases
 - when processing a explicitly coded POST (using deadline from the POST)
 - after successfully opening a Gate by NOTIFY (using deadline from Gate)

All other cases can now supply Time::NEVER, thereby indicating that
the processing layer shall use contextual information (intersection
of the time intervals)
2023-12-13 19:42:38 +01:00
3bf3ca095b Scheduler-test: failure of extended cascading notifications
...this is an interesting test failure, which highlights inconsistencies
with handling of deadlines when processing follow-up from NOTIFY-triggers

There was also some fuzziness related to the ''meaning'' of λ-post,
leading to at least one superfluous POST invocation for each propagation;
fixing this does not solve the problem yet removes unnecessary overhead
and lock-contention
2023-12-13 19:27:45 +01:00
fcde92a476 Scheduler-test: add node-weight statistics
...playing around with the graph for the Scheduler integration test
...single threaded run time seemed to behave irregular
...but in fact it is very close to what can be expected
   based on an ''averaged node weight''

Fortunately its very simple to add that into the existing node statistics
2023-12-12 20:51:31 +01:00
eef3525710 Scheduler-test: setup for integration test
Basically this is all done and settled already: this is the `usageExample()`
from `TestChainLoadTest`. However, the focus is slightly different here:
We want a demonstration that the Scheduler can work flawlessly through
a massive load. Thus the plan is to use much more challenging parameters,
and then lean back and watch what happens....
2023-12-12 19:21:15 +01:00
23a3a274ce Scheduler-test: investigate further breakage
...which turns out to be due to the DUMP-Statements,
which seem to create quite some contention on their own.

Test cases with very tight schedule will slip away then;
without print statement everything is GREEN now
2023-12-12 01:55:52 +01:00
b0bde3f0b2 Scheduler-test: fix bug in WorkForce scaling logic
this bug was there since the first draft, yet was covered
by another bug with the start-up logic.
And this latter one was fixed recently...

fa8622805

As a result, even when the COMPUTATION_CAPACITY is set to 0
still a single worker boots up (which should not be the case)

Solution: we do not need to "safeguard" against rounding errors,
since this is an internal implementation function, it is assumed
that the caller knows about its limitations...
2023-12-12 03:20:38 +01:00
da57e3dfcd Scheduler-test: ''can demonstrate running a synthetic load'' (closes #1346)
* added benchmark over synchronous execution as point of reference
 * verified running times and execution pattern
 * Scheduler **behaves as expected** for this example
2023-12-11 23:53:25 +01:00
347b9b24be Scheduler-test: complete and integrate computational load
This basically completes the Chain-Load framework;
a simple Scheduler integration run with all relevant features
can now be demonstrated.
2023-12-11 19:42:23 +01:00
db1ff7ded7 Scheduler-test: incremental calibration of both variants
- Generally speaking, the calibration uses current baseline settings;
- There are now two different load generation methods, thus both must be calibrated
- Performance contains some socked and non-linear effects, thus calibration
  should be done close to the work point, which can be achieved by incremental
  calibration until the error is < 5%

Interestingly, longer time-base values run slightly faster than predicted,
which is consistent with the expectation (socket cost). And using a larger
memory block increases time values, which is also plausible, since
cache effects will be diminishing
2023-12-11 04:43:05 +01:00
9ef8e78459 Scheduler-test: implement memory-accessing load
...use an array of volatiles, and repeatedly add neighbouring cells
...bake the base allocation size configurable, and tie the alloc to the scale-step
2023-12-11 03:13:28 +01:00
df4ee5e9c1 Scheduler-test: implement pure computation load
..initial gauging is a tricky subject,
since existing computer's performance spans a wide scale

Allowing
 - pre calibration -98% .. +190%
 - single run ±20%
 - benchmark ±5%
2023-12-11 03:10:42 +01:00
beebf51ac7 Scheduler-test: draft a configurable CPU load component
...which can be deliberately attached (or not attached) to the
individual node invocation functor, allowing to study the effect
of actual load vs. zero-load and worker contention
2023-12-10 19:58:18 +01:00
7847e3156c Scheduler-test: investigate processing patterns
Some test-runs performed excitingly smooth,
but in one case the processing was was drastically delayed,
due to heavy contention. The relevance of this incident is not clear yet,
since this test run uses a rather atypical load with very short actual work jobs.

Anyway, the dump-logs are documented with this commit.
2023-12-09 04:30:35 +01:00
fcfdf97853 Chain-Load: prepare infrastructure for computational load
Within Chain-Load, the infrastructure to add this crucial feature
is minimal: each node gets a `weight` parameter, which is assigned
using another RandomDraw-Rule (by default `weight==0`).

The actual computation load will be developed as a separate component
and tied in from the node calculation job functor.
2023-12-09 03:13:48 +01:00
fe6f2af7bb Chain-Load: combine all exit-hashes into a single global hash
...during development of the Chain-Load, it became clear that we'll often
need a collection of small trees rather than one huge graph. Thus a rule
for pruning nodes and finishing graphs was added. This has the consequence
that there might now be several exit nodes scattered all over the graph;
we still want one single global hash value to verify computations,
thus those exit hashes must now be picked up from the nodes and
combined into a single value.

All existing hash values hard coded into tests must be updated
2023-12-09 02:36:14 +01:00
9e25283b72 Chain-Load: precise pre-roll for planning-job
...with this change, processing is ''ahead of schedule'' from the beginning,
which has the nice side effect that the problematic contention situation
with these very short computation jobs can not arise, and most of the schedule
is processed by a single worker.

Processing pattern is now pretty much as expected
2023-12-09 01:20:53 +01:00
1df328cfc1 Chain-Load: switch planning chunk-size from level to node
This is a trick to get much better scheduling and timing guesses.
Instead of targeting a specific level, rather a fixed number of nodes
is processed in each chunk, yet still always processing complete levels.

The final level number to expect can be retrieved from the chain-load graph.

With this refactoring, we can now schedule a wake-up job precisely
after the expected completion of the last level
2023-12-08 23:52:57 +01:00
34d6423660 Scheduler-test: **first successful complete run**
Scheduling a wake-up job behind the end of the planned schedule did the trick.
Sometimes there is ''strong contention'' immediately after full provision of the WorkForce,
but this seems to be as expected, since the »Jobs« currently used have no
actually relevant run time on their own. It is even more surprising that
the Capacity-control logic is able to cope with this situation in a matter
of just some milliseconds, bringing the average Lag at ~ 300µs
2023-12-08 04:22:12 +01:00
7eca3ffe42 Scheduler-test: a helper for one-time operations
Invent a special JobFunctor...
 - can be created / bound from a λ
 - self-manages its storage on the heap
 - can be invoked once, then discards itself

Intention is to pass such one-time actions to the Scheduler
to cause some ad-hoc transitions tied to curren circumstances;
a notable example will be the callback after load-test completion.
2023-12-08 03:16:57 +01:00
030e9aa8a2 Scheduler / Activity-Lang: simplify handling of blocked Gate
In the first draft version, a blocked Gate was handled by
»polling« the Gate regularly by scheduling a re-invocation
repeatedly into the future (by a stepping defined through
ExecutionCtx::getWaitDelay()).

Yet the further development of the Activity-Language indicates
that the ''Notification mechanism'' is sufficient to handle all
foreseeable aspects of dependency management. Consequently this
''Gate poling is no longer necessary,'' since on Notification
the Gate is automatically checked and the activation impulse
is immediately passed on; thus the re-scheduled check would
never get an opportunity actually to trigger the Gate; such
an active polling would only be necessary if the count down
latch in the Gate is changed by "external forces".

Moreover, the first Scheduler integration tests with TestChainLoad
indicate that the rescheduled polling can create a considerable
additional load when longer dependency chains miss one early
prerequisite, and this additional load (albeit processed
comparatively fast by the Scheduler) will be shifted along
needlessly for quite some time, until all of the activities
from the failed chain have passed their deadline. And what
is even more concerning, these useless checks have a tendency
to miss-focus the capacity management, as it seems there is
much work to do in a near horizon, which in fact may not be
the case altogether.

Thus the Gate implementation is now *changed to just SKIP*
when blocked. This helped to drastically improve the behaviour
of the Scheduler immediately after start-up -- further observation
indicated another adjustment: the first Tick-duty-cycle is now
shortened, because (after the additional "noise" from gate-rescheduling
was removed), the newly scaled-up work capacity has the tendency
to focus in the time horizon directly behind the first jobs added
to the timeline, which typically is now the first »Tick«.

🡆 this leads to a recommendation, to arrange the first job-planning
chunk in such a way that the first actual work jobs appear in the area
between 5ms and 10ms after triggering the Scheduler start-up.Scheduler¡†
2023-12-07 22:12:41 +01:00
5abab5390d Scheduler: start-up working -- no need for pre-delay
Introducing a fixed pre-delay on each new Calc-Streem seemed like an obvious remedy,
yet on closer investigation it turned out that the start-up logic as such was contradictory,
which was only uncovered by some rather special schedule patterns.

After fixing the logic deficiencies, Scheduler starts up as intended
and the probabilistic capacity-control seems to work as designed.

Thus no need to introduce an artificial delay at begin, even while
this implies that typically the first round of job-planning will be
performed synchronous, in the invoking thread (which may be surprising,
but is completely within the limits of the architecture; we do not
employ specifically configured threads and planning should be done
in short chunks, thus the first chunk can well be done by the caller)
2023-12-07 21:02:39 +01:00
fa86228057 Scheduler: rework load-regulation
The first complete integration test with Chain-Load
highlighted some difficulties with the overall load regulation:
- it works well in the standard case (but is possibly to eager to scale up)
- the scale-up sometimes needs several cycles to get "off the ground"
- when the first job is dispatched immediately instead of going
  through the queue, the scheduler fails to boot up
2023-12-07 03:55:20 +01:00
e761447a25 Chain-Load: setup simple integration test
- use a chain-load with 64 steps
- use a simple topology
- trigger test run with default stepping

TODO: Test hangs -> Timeout
2023-12-06 07:24:30 +01:00
481e35a597 Chain-Load: implement translation into Scheduler invocations
... so this (finally) is the missing cornerstone
... traverse the calculation graph and generate render jobs
... provide a chunk-wise pre-planning of the next batch
... use a future to block the (test) thread until completed
2023-12-06 01:54:35 +01:00
5e9b115283 Chain-Load: verify correct operation of planning logic
- test setup without actual scheduler
- wire the callbacks such to verify
  + all nodes are touched
  + levels are processed to completion
  + the planning chunk stops at the expected level
  + all node dependencies are properly reported through the callbacks
2023-12-05 01:31:54 +01:00
29ca3a485f Chain-Load: implement planning JobFunctor
- decided to abstract the scheduler invocations as λ
- so this functor contains the bare loop logic

Investigation regarding hash-framework:
It turns out that boost::hash uses a different hash_combine,
than what we have extracted/duplicated in lib/hash-value.hpp
(either this was a mistake, or boost::hash did use this weaker
 function at that time and supplied a dedicated 64bit implementation later)

Anyway, should use boost::hash for the time being
maybe also fix the duplicated impl in lib/hash-value.hpp
2023-12-04 16:29:57 +01:00
2e6712e816 Chain-Load: implement invocation through JobFunctor
- use a ''special encoding'' to marshal the specific coordinates for this test setup
- use a fixed Frame-Grid to represent the ''time level''
- invoke hash calculation through a specialised JobFunctor subclass
2023-12-04 03:57:04 +01:00
7d5242f604 Chain-Load: remove excess template argument
The number of nodes was just defined as template argument
to get a cheap implementation through std::array...

But actually this number of nodes is ''not a characteristics of the type;''
we'd end up with a distinct JobFunctor type for each different test size,
which is plain nonsensical. Usage analysis reveals, now that the implementation
is ''basically complete,'' that all of the topology generation and statistic
calculation code does not integrate deeply with the node storage, but
rather just iterates over all nodes and uses the ''first'' and ''last'' node.
This can actually be achieved very easy with a heap-allocated plain array,
relying on the magic of lib::IterExplorer for all iteration and transformation.
2023-12-04 04:16:16 +01:00
e0766f2262 Chain-Load: draft usage for Scheduler testing
- use a dedicated context "dropped off" the TestChainLoad instance
- encode the node-idx into the InvocationInstanceID
- build an invocation- and a planning-job-functor
- let planning progress over an lib::UninitialisedStorage array
- plant the ActivityTerm instances into that array as Scheduling progresses
2023-12-04 00:34:06 +01:00
c5679b0fd0 Library: Uninitialised-Storage array (see #1204)
Introduced as remedy for a long standing sloppiness:
Using a `char[]` together with `reinterpret_cast` in storage management helpers
bears danger of placing objects with wrong alignment; moreover, there are increasing
risks that modern code optimisers miss the ''backdoor access'' and might apply too
aggressive rewritings.

With C++17, there is a standard conformant way to express such a usage scheme.
 * `lib::UninitialisedStorage` can now be used in a situation (e.g. as in `ExtentFamily`)
   where a complete block of storage is allocated once and then subsequently used
   to plant objects one by one
 * moreover, I went over the code base and adapted the most relevant usages of
   ''placement-new into buffer'' to also include the `std::launder()` marker
2023-12-02 23:56:46 +01:00
6707962bca Chain-Load: work out a set of comprehensible example patterns
Since Chain-Load shall be used for performance testing of the scheduler,
we need a catalogue of realistic load patterns. This extended effort
started with some parameter configurations and developed various graph
shapes with different degree of connectivity and concurrency, ranging
from a stable sequence of very short chains to large and excessively
interconnected dependency networks.
2023-12-01 23:43:00 +01:00
bb69cf02e3 Chain-Load: demonstrate pruning and separated graph segments
Through introduction of a ''pruning rule'', it is possible
to create exit nodes in the middle of the graph. With increased
intensity of pruning, it is possible to ''choke off'' the generation
and terminate the graph; in such a case a new seed node is injected
automatically. By combination with seed rules, an equilibrium of
graph start and graph termination can be achieved.

Following this path, it should be possible to produce a pattern,
which is random but overall stable and well suited to simulate
a realistic processing load.

However, finding proper parameters turns out quite hard in practice,
since the behaviour is essentially contingent and most combinations
either lead to uninteresting trivial small graph chunks, or to
large, interconnected and exponentially expanding networks
2023-12-01 04:50:11 +01:00
38f27f967f Chain-Load: demonstrate seeding new chains
... seeding happens at random points in the middle of the chain
... when combined with reduction, the resulting processing pattern
    resembles the real processing pattern of media calcualtions
2023-11-30 21:06:10 +01:00
229541859d Chain-Load: demonstrate use of reduction rule
... special rule to generate a fixed expansion on each seed
... consecutive reductions join everything back into one chain
... can counterbalance expansions and reductions
2023-11-30 03:20:23 +01:00
aafd277ebe Chain-Load: rework the pattern for dynamic rules
...as it turns out, the solution embraced first was the cleanest way
to handle dynamic configuration of parameters; just it did not work
at that time, due to the reference binding problem in the Lambdas.
Meanwhile, the latter has been resolved by relying on the LazyInit
mechanism. Thus it is now possible to abandon the manipulation by
side effect and rather require the dynamic rule to return a
''pristine instance''.

With these adjustments, it is now possible to install a rule
which expands only for some kinds of nodes; this is used here
to crate a starting point for a **reduction rule** to kick in.
2023-11-30 02:13:39 +01:00
3d5fdce1c7 Chain-Load: demonstrate use of the expansion rule
...played a lot with the parameters
...behaviour and DOT graphs look plausible
...document three typical examples with statistics
2023-11-29 02:58:55 +01:00
dd6929ccc5 Chain-Load: validate and improve statistics
- present the weight centres relative to overall level count
- detect sub-graphs and add statistics per subgraph
- include an evaluation for ''all nodes''
- include number of levels and subgraphs
2023-11-28 22:46:59 +01:00
852a86bbda Chain-Load: generate statistics report
...test and fix the statistics computation...
2023-11-28 16:25:22 +01:00
c3bef6d344 Chain-Load: implement graph statistic computation
- iterate over all nodes and classify them
- group per level
- book in per level statistics into the Indicator records
- close global averages

...just coded, not yet tested...
2023-11-28 03:03:55 +01:00
d968da989e Chain-Load: define data structure for graph statistics
The graph will be used to generate a computational load
for testing the Scheduler; thus we need to compute some
statistical indicators to characterise this load.

As starting point sum counts and averages will be aggregated,
accounting for particular characterisation of nodes per level.
2023-11-28 02:18:38 +01:00
a780d696e5 Chain-Load: verify connectivity and recalculation
It seams indicated to verify the generated connectivity
and the hash calculation and recalculation explicitly
at least for one example topology; choosing a topology
comprised of several sub-graphs, to also verify the
propagation of seed values to further start-nodes.

In order to avoid addressing nodes directly by index number,
those sub-graphs can be processed by ''grouping of nodes'';
all parts are congruent because topology is determined by
the node hashes and thus a regular pattern can be exploited.

To allow for easy processing of groups, I have developed a
simplistic grouping device within the IterExplorer framework.
2023-11-27 21:58:37 +01:00
619a5173b0 Chain-Load: handle node seed and recalculation
- with the new pruning option, start-Nodes can now be anywhere
- introduce predicates to detect start-Nodes and exit-Nodes
- ensure each new seed node gets the global seed on graph construction
- provide functionality to re-propagate a seed and clear hashes
- provide functionality to recalculate the hashes over the graph
2023-11-26 22:28:12 +01:00
1ff9225086 Chain-Load: ability to prune chains
...using an additional pruneRule...
...allows to generate a wood instead of a single graph
...without shuffling, all part-graphs will be identical
2023-11-26 20:57:13 +01:00
5af2279271 Chain-Load: ability to inject further shuffling
up to now, random values were completely determined by the
Node's hash, leading to completely symmetrical topology.
This is fine, but sometimes additional randomness is desirable,
while still keeping everything deterministic; the obvious solution
is to make the results optionally dependent on the invocation order,
which is simply to achieve with an additional state field. After some
tinkering, I decided to use the most simplistic solution, which is
just a multiplication with the state.
2023-11-26 19:46:48 +01:00
ecbe5e5855 Chain-Load: generate new start node automatically
this is only a minor rearrangement in the Algorithm,
but allows to re-boot computation should node connectivity
go to zero. With current capabilities, this could not happen,
but I'm considering to add a »pruning« parameter to create the
possibility to generate multiple shorter chains instead of one
complete chain -- which more closely emulates reality for
Scheduler load patterns.
2023-11-26 18:25:10 +01:00
dbe71029b7 Chain-Load: now able to define RandomDraw rules
...all existing tests reproduced
...yet notation is hopefully more readable

Old:
  graph.expansionRule([](size_t h,double){ return Cap{8, h%16, 63}; })

New:
  graph.expansionRule(graph.rule().probability(0.5).maxVal(4))
2023-11-26 03:04:59 +01:00
f1c156b4cd Chain-Load: lazy init of functional configuration now complete
...so this was yet another digression, caused by the desire
somehow to salvage this problematic component design. Using a
DSL token fluently, while internally maintaining a complex and
totally open function based configuration is a bit of a stretch.
2023-11-25 23:47:20 +01:00
659441fa88 Chain-Load: verify (and bugfix) 2023-12-03 04:59:18 +01:00
ed8d9939bd Chain-Load: provide a scheme for repeated init
For context: I've engaged into writing a `LazyInit` helper component,
to resolve the inner contradiction between DSL use of `RandomDraw`
(implying value semantics) and the design of a processing pipeline,
which quite naturally leads to binding by reference into the enclosing
implementation.

In most cases, this change (to lazy on-demand initialisation) should be
transparent for the complete implementation code in `RandomDraw` -- with
one notable exception: when configuring an elaborate pipeline, especially
with dynamic changes of the probability profile during the simulation run,
then then obviously there is the desire to use the existing processing
pipeline from the reconfiguration function (in fact it would be quite
hard to explain why and where this should be avoided). `LazyInit` breaks
this usage scenario, since -- at the time the reconfiguration runs --
now the object is not initialised at all, but holds a »Trojan« functor,
which will trigger initialisation eventually.

After some headaches and grievances (why am I engaging into such an
elaborate solution for such an accidental and marginal topic...),
unfortunately it occurred to me that even this problem can be fixed,
with yet some further "minimal" adjustments to the scheme: the LazyInit
mechanism ''just needs to ensure'' that the init-functor ''sees the
same environment as in eager init'' -- that is, it must clear out the
»Trojan« first, and it ''could apply any previous pending init function''
fist. That is, with just a minimal change, we possibly build a chain
of init functors now, and apply them in given order, so each one
sees the state the previous one created -- as if this was just
direct eager object manipulation...
2023-12-03 04:59:18 +01:00
04ca79fd65 Chain-Load: verify re-initialisation and copy
...this is a more realistic demo example, which mimics
some of the patterns present in RandomDraw. The test also
uses lambdas linking to the actual storage location, so that
the invocation would crash on a copy; LazyInit was invented
to safeguard against this, while still allowing leeway
during the initialisation phase in a DSL.
2023-12-03 04:59:18 +01:00
e95f729ad0 Chain-Load: verify simple usage of LazyInit
...turns out I'd used the wrong Opaque buffer component;
...but other than that, the freaky mechanism seems to work
2023-12-03 04:59:18 +01:00
c658512d7b Chain-Load: verify building blocks of lazy-init 2023-12-03 04:59:18 +01:00
b00f4501a3 Chain-Load: draft the lazy-init mechanism
...oh my.
This is getting messy. I am way into danger territory now....
I've made a nifty cool design with automatically adapted functors;
yet at the end of the day, this does not bode well with a DSL usage,
where objects appear to be simple values from a users point of view.
2023-12-03 04:59:18 +01:00
8de3fe21bb Chain-Load: detect small-object optimisation
- Helper function to find out of two objects are located
  "close to each other" -- which can be used as heuristics
  to distinguish heap vs. stack storage

- further investigation shows that libstdc++ applies the
  small-object optimisation for functor up to »two slots«
  in size -- but only if the copy-ctor is trivial. Thus
  a lambda capturing a shared_ptr by value will *always*
  be maintained in heap storage (and LazyInit must be
  redesigned accordingly)...

- the verify_inlineStorage() unit test will now trigger
  if some implementation does not apply small-object optimisation
  under these minimal assumptions
2023-12-03 04:59:18 +01:00
98078b9bb6 Chain-Load: investigate std::function inline-storage
...which is crucial for the solution pursued at the moment;
std::function is known to apply a small-object optimisation,
yet unfortunately there are no guarantees by the C++ standard
(it is only mandated that std::function handles a bare function
 pointer without overhead)

Other people have investigated that behaviour already,
indicating that at least one additional »slot« of data
can be handled with embedded storage in all known implementations
(while libstdc++ seemingly imposes the strongest limitations)
https://stackoverflow.com/a/77202545/444796

This experiment in the unit-test shows that for my setup
(libstdc++ and GCC-8) only a lambda capturing a single pointer
is handled entirely embedded into the std::function; already
a lambda capturing a shared-ptr leads to overflow into heap
2023-12-03 04:59:18 +01:00
3c713a4739 Chain-Load: invent the heart of the trap-mechanism
...the intention is to plant a »trojan lambda« into the target functor,
to set off initialisation (and possibly relocation) on demand.
2023-12-03 04:59:18 +01:00
1892d1beb5 Chain-Load: safety problems with rule initialisation
the RandomDraw rules developed last days are meant to be used
with user-provided λ-adapters; employing these in a context
of a DSL runs danger of producing dangling references.

Attempting to resolve this fundamental problem through
late-initialisation, and then locking the component into
a fixed memory location prior to actual usage. Driven by
the goal of a self-contained component, some advanced
trickery is required -- which again indicates better
to write a library component with adequate test coverage.
2023-12-03 04:59:18 +01:00
5033674b00 Chain-Load: define bindings to use the new RandomDraw component
RandomDraw as a library component was extracted and (grossly) augmented
to cut down the complexity exposed to the user of TestChainLoad.
To control the generated topology, random-selected parameters
must be configured, defining a probability profile; while
this can be achieved with simple math, getting it correct
turned out surprisingly difficult.
2023-12-03 04:59:18 +01:00
8b1326129a Library: RandomDraw - implementation complete and tested. 2023-12-03 04:59:17 +01:00
3808166494 Library: RandomDraw - invent new scheme for dynamic configuration
...now using the reworked partial-application helper...
...bind to *this and then recursively re-invoke the adaptation process
...need also to copy-capture the previously existing mapping-function

first test seems to work now
2023-12-03 04:59:17 +01:00
32b740cd40 Library: RandomDraw - dynamic configuration requires partial application
Investigation in test setup reveals that the intended solution
for dynamic configuration of the RandomDraw can not possibly work.
The reason is: the processing function binds back into the object instance.
This implies that RandomDraw must be *non-copyable*.

So we have to go full circle.
We need a way to pass the current instance to the configuration function.
And the most obvious and clear way would be to pass it as function argument.
Which however requires to *partially apply* this function.

So -- again -- we have to resort to one of the functor utilities
written several years ago; and while doing so, we must modernise
these tools further, to support perfect forwarding and binding
of reference arguments.
2023-12-03 04:59:17 +01:00
75cbfa8991 Library: RandomDraw - adaptor and mapping functions
...the beautiful thing with functions and Metaprogramming is:
it mostly works as designed out of the box, once you make it
past the Compiler.
2023-11-22 04:26:22 +01:00
2578df7c1d Library: RandomDraw - verify numerics (II)
- strive at complete branch coverage for the mapping function
- decide that the neutral value can deliberately lie outside
  the value range, in which case the probability setting
  controls the number of _value_ result incidents vs
  neutral value result incidents.
- introduce a third path to define this case clearly
- implement the range setting Builder-API functions
- absorb boundrary and illegal cases
2023-11-22 02:36:34 +01:00
4f28e8ad6c Library: RandomDraw - verify numerics (I)
- use a Draw with only a few values
- but with an origin within the value range
- verify stepping and distributions for various probabilities
2023-11-21 22:07:51 +01:00
bdb2f12b80 Library: RandomDraw - use dynamic quantiser
For sake of simplicity, since this whole exercise is a byproduct,
the mapping calculations are done in doubles. To get even distribution
of values and a good randomisation, it is thus necessary to break
down the size_t hash value in a first step (size_t can be 64bit
and random numbers would be subject to rounding errors otherwise)

The choice of this quantiser is tricky; it must be a power of two
to guarantee even distribution, and if chosen to close to the grid
of the result values, with lower probabilities we'd fail to cover
some of the possible result values.  If chosen to large, then
of course we'd run danger of producing correlated numbers on
consecutive picks.

Attempting to use 4 bits of headroom above the log-2 of the
required value range. For example, 10-step values would use
a quantiser of 128, which looks like a good compromise.
The following tests will show how good this choice holds up.
2023-11-21 19:50:22 +01:00
5b9a463b38 Library: RandomDraw - rework mapping rule to support origin
The first step was to allow setting a minimum value,
which in theory could also be negative (at no point is the
code actually limited to unsigned values; this is rather
the default in practice).

But reconsidering this extensions, then you'd also want
the "neutral value" to be handled properly. Within context,
this means that the *probability* controls when values other
than the neutral value are produced; especially with p = 1.0
the neutral value shall not be produced at all
2023-11-21 17:49:50 +01:00
75dd4210f2 Library: RandomDraw - must accept generic arguments
...since the Policy class now defines the function signature,
we can no longer assume that "input" is size_t. Rather, all
invocations must rely on the generic adaptaion scheme.

Getting this correct turns out rather tricky again;
best to rely on a generic function-composition.

Indeed I programmed such a helper several years ago,
with the caveat that at that time we used C++03 and
could not perfect-forward arguments. Today this problem
can be solved much more succinct using generic Lambdas.
2023-11-21 04:07:30 +01:00
651e28bac9 Library: RandomDraw - introduce policy template
to define this as a generic library component,
any reference to the actual data source moust be extracted
from the body of the implementation and supplied later
at usage site. In the actual case at hand the source
for randomness would be the node hash, and that is
absolutely an internal implementation detail.
2023-11-20 21:05:18 +01:00
605c1b4a17 Library: RandomDraw - consolidate prototype
...still same functionality as established yesterday in experimentation (try.cpp)
2023-11-20 18:49:00 +01:00
e5f5953b15 Library: RandomDraw - extract as generic component
The idea is to use some source of randomness to pick a
limited parameter value with controllable probability.
While the core of the implementation is nothing more
than some simple numeric adjustments, these turn out
to be rather intricate and obscure; the desire to
package these technicalities into a component
however necessitates to make invocations
at usage site self explanatory.
2023-11-20 16:38:55 +01:00
e127d0ad9a Chain-Load: develop a design for a random-number-drawing DSL
This might seem totally overblown -- but already the development
of this prototype showed me time and again, that it is warranted.
Because it is damn hard to get the probabilities and the mappings
to fixed output values correct.

After in-depth analysis, I decided completely to abandon the
initially chosen approach with the Cap helper, where the user
just specifies an upper and lower bound. While this seems
compellingly simple at start, it directly lures into writing
hard-to-understand code tied to the implementation logic.

With the changed approach, most code should get along rather with

auto myRule = Draw().probabilty(0.6).maxVal(4);

...which is obviously a thousand times more legible than
any kind of tricky modulus expressions with shifted bounds.
2023-11-20 03:16:23 +01:00
34f1b0da89 Chain-Load: investigate ways for notation of topology rules
While the Cap-Helper introduced yesterday was already a step in the
right direction, I had considerable difficulties picking the correct
parameters for the upper/lower bounds and the divisor for random generation
so as to match an intended probability profile. Since this tool shall be
used for load testing, an easier to handle notation will both help
with focusing on the main tasks and later to document the test cases.

Thus engaging (again) into the DSL building game...
2023-11-19 23:12:54 +01:00
0686c534cf Chain-Load: verify topology building -- and fix a Bug
...start with putting the topology generator to work

- turns out it is still challenging to write the ctrl-rules
- and one example tree looked odd in the visualisation
- which (on investigation) indicated unsound behaviour

...this is basically harmless, but involves an integer wrap-around
in a variable not used under this conditions (toReduce), but also
a rather accidental and no very logical round-up of the topology.

With this fix, the code branch here is no longer overloaded with two
distinct concerns, which I consider an improvement
2023-11-17 18:54:51 +01:00
960c461bb4 Chain-Load: verify simple linear hash-chain
by default, a linear chain without any forking is generated,
and the result hash is computed by hash-chaining from the seed.

Verify proper connections and validate computed hash
2023-11-17 02:15:50 +01:00
1f2a635973 Chain-Load: get the first non-trivial topology to work
..as can be expected, had do chase down some quite hairy problems,
especially since consumption of the fixed amount of nodes is not
directly linked to the ''beat'' of the main loop and thus boundary
conditions and exhausted storage can happen basically anywhere.

Used a simple expansion rule and got a nod graph,
which looks coherent in DOT visualisation.
2023-11-17 01:11:12 +01:00
686b98ff1e Chain-Load: mapping helper for control-rules
writing a control-value rule for topology generation typically
involves some modulus and then arthmetic operations to map
only part of the value range to the expected output range.

These calculations are generic, noisy and error-prone.
Thus introduce a helper type, which allows the client just
to mark up the target range of the provided value to map and
transform to the actually expected result range, including some
slight margin to absorb rounding errors. Moreover, all calculations
done in double, to avoid the perils of unsigned-wrap-around.
2023-11-16 21:38:06 +01:00
cc56117574 Chain-Load: integrate topology visualisation (DOT)
- provide as ''operator'' on the TestChainLink instance
- show shortened Node-Hash as label on each Node
2023-11-16 18:42:36 +01:00
76f250a5cf Library: extract Graphviz-DOT generation helpers
...these were developed driven by the immediate need
to visualise ''random generated computation patterns''
for ''Scheduler load testing.''

The abstraction level of this DSL is low
and structures closely match some clauses of the DOT language;
this approach may not yet be adequate to generate more complex
graph structures and was extracted as a starting point
for further refinements....
2023-11-16 17:20:36 +01:00
1c4b1a2973 Chain-Load: draft - generate DOT diagram from calculation topology
With all the preceding DSL work, this turns out to be surprisingly easy;
the only minor twist is the grouping of nodes into (time)levels,
which can be achieved with a "lagging" update from the loop body

Note: next step will be to extract the DSL helpers into a Library header
2023-11-16 17:19:29 +01:00
65fa16b626 Chain-Load: work out DSL for generating DOT scripts
...using a pre-established example as starting point

It seems that building up this kind of generator code
from a set of free functions in a secluded namespace
is the way most suitable to the nature of the C++ language
2023-11-16 03:19:19 +01:00
1c392eeae3 Chain-Load: explore ways to visualise topology
..the idea is to generate a Graphviz-DOT diagram description
by traversing the internal data structures of TestChainLoad.

- refreshed my Graphviz knowledge
- work out a diagram scheme that can be easily generated
- explore ways to structure code generation as a DSL to keep it legible
2023-11-15 03:09:36 +01:00
aa3c25e092 Chain-Load: implement generation mechanism
...introduce statistical control functions (based on hash)
...add processing stage for current set of nodes
...process forking, reduction and injection of new nodes
2023-11-12 23:31:08 +01:00
60dc34a799 Chain-Load: skeleton of topology-generation
...use a pass over the nodes, with some alternating set
of current and next nodes, which are to be connected
2023-11-12 19:36:27 +01:00
ea84935f2a Chain-Load: improve Node-link storage
- use a specialised class, layered on top of std::array
- use additional storage to mark filling degree
- check/fail on link owerflow directly there

We still use fixed size inline storage for the node links,
yet adding this comparatively small overhead in storage helps
getting the code simpler and adding links is now constant-complexity
2023-11-12 16:56:39 +01:00
7bc2c80d3a Chain-Load: calculation node - basic properties
A »Node« represents one junction point in the dependency graph,
knows his predecessors and successors and carries out one step
of the chained hash calculation.
2023-11-12 04:14:11 +01:00
3ff25c1e9f Chain-Load: design considerations
...develop the idea for building the necessary DAG data structure...
2023-11-12 03:02:49 +01:00
c8f13ca3e6 Chain-Load: initial draft
...design a pattern to generate a reproducible computation load
2023-11-11 01:05:54 +01:00
3135887914 Scheduler: connect BlockFlow capacity announcement
...refine the handling of FrameRates close to the definition bounds
...implement the actual rule to scale allocator capacity on announcement
...hook up into the seedCalcStream() with a default of +25FPS

+ test coverage
2023-11-10 23:52:20 +01:00
a2a960f544 Scheduler: look for ways to propagate a capacity-hint
...whenever a new CalcStream is seeded, it would be prudent
not only to step up the WorkForce (which is already implemented),
but also to provide a hint to the BlockFlow allocator regarding
the expected calculation density.

Such a hint would allow to set a more ample »epoch« spacing,
thereby avoiding to drive the allocator into overload first.
The allocator will cope anyway and re-balance in a matter of
about 2 seconds, but avoiding this kind of control oscillations
altogether will lead to better performance at calculation start.
2023-11-10 05:14:55 +01:00
ecf1a5a301 Scheduler: implement the remaining API functions
...this completes the definition of the Scheduler-Service implementation
2023-11-10 05:07:49 +01:00
2baf058198 Scheduler: high-level schedule-Render-Job test complete 2023-11-09 04:04:53 +01:00
5c6354882d Scheduler: solve problem with transport from entrance-queue
The test case "scheduleRenderJob()" -- while deliberately operated
quite artificially with a disabled WorkForce (so the test can check
the contents in the queue and then progress manually -- led to discovery
of an open gap in the logic: in the (rare) case that a new task is
added ''from the outside'' without acquiring the Grooming-Token, then
the new task could sit in the entrace queue, in worst case for 50ms,
until the next Scheduler-»Tick« routinely sweeps this queue. Under
normal conditions however, each dispatch of another activity will
also sweep the entrance queue, yet if there happens to be no other
task right now, a new task could be stuck.

Thinking through this problem also helped to amend some aspects
of Grooming-Token handling and clarified the role of the API-functions.
2023-11-08 20:58:32 +01:00
449b5c8f50 Engine: draft a messaging interface for EngineObserver (see #1347)
For now, the `EngineObserver` is defined as an empty shell,
outfitted with a low-level binary message dispatch API.

Messages are keyed by a Symbol, which allows evolution of private message types.
Routing and Addressing is governed by an opaque size_t hash.
The `EngineEvent` data base class provides »4 Slots« of inline binary storage;
concrete subclasses shall define the mapping of actual data into this space
and provide a convenience constructor for events.

For use by the Scheduler, a `WorkTiming`-Event is defined based on this scheme;
this allows to implement the λ-work and λ-done of the Scheduler-`ExecutionCtx`.
These hooks will be invoked at begin and end of any render calculations.
2023-11-08 04:40:32 +01:00
892099412c Scheduler: integrate sanity check on timings
...especially to prevent a deadline way too far into the future,
since this would provoke the BlockFlow (epoch based) memory manager
to run out of space.

Just based on gut feeling, I am now imposing a limit of 20seconds,
which, given current parametrisation, with a minimum spacing of 6.6ms
and 500 Activities per Block would at maximum require 360 MiB for
the Activities, or 3000 Blocks. With *that much* blocks, the
linear search would degrade horribly anyway...
2023-11-07 18:37:20 +01:00
0ed7dba641 Scheduler: automatically step up capacity on new task
WorkForce scales down automatically after 2 seconds when
workers fall idle; thus we need to step up automatically
with each new task.

Later we'll also add some capacity management to both the
LoadController and the Job-Planning, but for now this rather
crude approach should suffice.

NOTE: most of the cases in SchedulerService_test verify parts
of the component integration and thus need to bypass this
automatism, because the test code wants to invoke the
work-Function directly (without any interference
from running workers)
2023-11-07 17:00:24 +01:00
8056bebf9c Scheduler: allow to manipulate nominal full capacity
While building increasingly complex integration tests for the Scheduler,
it turns out helpful to be able to manipulate the "full concurreency"
as used by Scheduler, WorkForce and LoadController.

In the current test, I am facing a problem that new entries from the
threadsafe entrance queue are not propagated to the priority queue
soon enough; partly this is due to functionality still to be added
(scaling up when new tasks are passed in) -- but this will further
complicate the test setup.
2023-11-07 16:12:56 +01:00
86a909b850 Scheduler: implement the render job builder
...simply by delegating to the underlying builder notation
on activity::Term as provided by the Activity-Language
2023-11-06 23:54:46 +01:00
86b90fbf84 Scheduler: draft high-level API for building a Job schedule
The invocation structure is effectively determined by the
Activity-chain builder from the Activity-Language; but, taking
into account the complexity of the Scheduler code developed thus far,
it seems prudent to encapsulate the topic of "Activities" altogether
and expose only a convenience builder-API towards the Job-Planning
2023-11-06 06:00:00 +01:00
c377ac7d46 Scheduler: observe start and deadline explicitly given by POST
With the previous change, we allways have an execution scope now,
which (among other things) defines a time-window (start,deadline).
However, the entrance point to an Activity-chain, the POST-Activity
also defines a time window, which is now combined with this scope
by maximum / minimum constraining.
2023-11-06 04:18:00 +01:00
72258c06bd Scheduler: reconciled into clearer design
The problem with passing the deadline was just a blatant symptom
that something with the overall design was not quite right, leading
to mix-up of interfaces and implementation functions, and more and more
detail parameters spreading throughout the call chains.

The turning point was to realise the two conceptual levels
crossing and interconnected within the »Scheduler-Service«

- the Activity-Language describes the patterns of processing
- the Scheduler components handle time-bound events

So by turning the (previously private) queue entry into an
ActivationEvent, the design could be balanced.
This record becomes the common agens within the Scheduler,
and builds upon / layers on top of the common agens of the
Language, which is the Activity record.
2023-11-04 04:49:13 +01:00
62a1310566 Scheduler: rearrange internal API to expose context data
This is the first step to address the conceptual problems identified yesterday,
and works largely as a drop-in replacement. Instead of just retrieving
the Activity*, now the Queue entry itself is exposed to the rest of the
scheduler implementation, augmented with implicit conversion, allowing
all of the tests to remain unaltered (and legible, without boilerplate)
2023-11-04 01:59:42 +01:00
747e522c7e Scheduler: design-problems while integrating deadline
the attempt to integrate additional deadline and significance parameters
unveils a design problem due to the layering of contexts

- the Activity-Language attempts to abstract away the ''Scheduler mechanics''
- but this implementation logic now needs to pass additional parameters
- and notably there is the possibility of direct re-scheduling from within
  the Activity-Dispatch

The symptom of this problem is that it's no longer possible
to implement the ExecutionCtx.post() function in the real Scheduler-context
2023-11-03 03:33:23 +01:00
b49de0738d Scheduler: implement automatic clean-up of outdated entries
Hooked into the existing processing logic at Layer-2,
and relying on the information functions of Layer-1
2023-11-03 01:17:10 +01:00
b1e0ce1a79 Scheduler: define expected filtering behaviour for significant tasks 2023-11-03 00:31:33 +01:00
d622b59dfd Scheduler: support for classification data in Layer-1
- this is prerequisite to check for significance of the head entry
- implement and verify the information functions at Layer-1
2023-11-02 23:25:44 +01:00
7887941c89 Scheduler: prepare for dropping obsoleted entries
...it is clear that there must be a way to flush the scheduler queues
an thereby silently drop any obsoleted or irrelevant entries. This topic
turns out to be somewhat involved, as it requires to consider the
deadline (due to the memory management, which is based on deadlines).
Furthermore there is a relation to yet another challenging conceptual
requirement, which is the support for other operation modes beyond
just time-bound rendering; these concerns make it desirable to
expand the internal representation of entries in the queue.

Concerns regarding performance are postponed deliberately,
until we can demonstrate the Scheduler-Service running under
regular operational conditions.
2023-11-02 16:46:08 +01:00
5c5dc40f3f Scheduler: processing of peak loads works
This is the first kind of integration,
albeit still with a synthetic load.

- placed two excessive load peaks in the scheduling timeline
- verified load behaviour
- verified timings
- verified that the scheduler shuts down automatically when done
2023-11-01 04:24:44 +01:00
4937577557 (WIP) instrumentation for investigation of sleep-behaviour 2023-11-01 02:06:02 +01:00
9f7711d26b Scheduler: complete and cover load indicator
- sample distance to scheduler head whenever a worker asks for work
- moving average with N = worker-pool size and damp-factor 2
- multiply with the current concurrency fraction
2023-10-31 02:29:50 +01:00
a087e52ab1 Scheduler: draft a load indicator
...using a state fusion
based on both the threadpool size and the average distance
or lag to the next task to be scheduled.
2023-10-30 20:22:06 +01:00
4fada4225c Scheduler: watch behaviour under load
- create a synthetic load peak while operating with full WorkForce
- Goal is to develop a load indicator
2023-10-30 05:09:41 +01:00
22b4a9e4b2 Scheduler: start and shutdown implemented and demonstrated in test
- An important step towards a complete »Scheduler Service«
- Correct timing pattern could be verified in detail by tracing
- Spurred some further concept and design work regarding Load-control
2023-10-29 20:06:41 +01:00
8505059476 Scheduler: consider how to maintain active state
- draft the duty cycle »tick«
- investigate corner cases of state updates and allocation managment
- implement start and forcible stop of the scheduler service
2023-10-29 04:22:42 +01:00
4e9d54e6f9 Scheduler: switch to steady-clock
Obviously the better choice and a perfect fit for our requirements;
while the system-clock may jump and even move backwards on time service
adjustments, the steady clock just counts the ticks since last boot.

In libStdC++ both are implemented as int64_t and use nanoseconds resolution
2023-10-28 20:58:37 +02:00
6166ab63f2 Scheduler: complete handling of the grooming-token
- Ensure the grooming-token (lock) is reliably dropped
- also explicitly drop it prior to trageted sleeps
- properly signal when not able to acquire the token before dispatch

- amend tests broken by changes since yesterday
2023-10-28 05:35:35 +02:00
552d8dec0e Scheduler: complete work-Function / conception work
Notably the work-function is now completely covered, by adding
this last test, and the detailed investigations yesterday
ultimately unveiled nothing of concern; the times sum up.

Further reflection regarding the overall concept led me
to a surprising solution for the problem with priority classes.
2023-10-28 05:34:56 +02:00
e26d251867 Scheduler: rationalise delay decision logic
...especially for the case »outgoing to sleep«

- reorganise switch-case to avoid falling through
- properly handle the tendedNext() predicate also in boundrary cases
- structure the decision logic clearer
- cover the new behaviour in test

Remark: when the queue falls empty, the scheduler now sends each
worker once into a targted re-shuffling delay, to ensure the
sleep-cycles are statistically evenly spaced
2023-10-28 05:34:56 +02:00
b5e9d67a79 Scheduler: wrap-up and comment test cases thus far
...up to now, Behaviour is as expected
- with some minor discrepancies still to be fixed
- and an effect due to the test-scaffolding
2023-10-27 03:37:24 +02:00
097001d16f Scheduler: investigate timings of dispatch()
...there seemed to be an anomaly of 50...100µs

==> conclusion: this is due to the instrumentation code
    - it largely caused by the EventLog, which was never meant
      to be used in performance-critical code, and does hefty
      heap allocations and string processing.
    - moreover, there clearly is a cache-effect, adding a Factor 2
      whenever some time passed since the last EventLog call

==> can be considered just an artifact of the test setup and
    will have no impact on the scheduler


remark: this commit adds a lot of instrumentation code
2023-10-27 02:53:34 +02:00
a90a5d9636 Scheduler: can demonstrate basic behaviour
- invoked right away
- pre-sleep to tend next
- post-sleep if next activity follows at a distance
2023-10-26 03:56:18 +02:00
a71bcaae43 Scheduler: shorthand notation for work-Function test
To cover the visible behaviour of the work-Function,
we have to check an amalgam of timing delays and time differences.

This kind of test tends to be problematic, since timings are always
random and also machine dependent, and thus we need to produce pronounced effects
2023-10-26 01:14:13 +02:00
5164ead929 Scheduler: access invocation time for test
...find a way to sneak out the "now" parameter passed on Invocation
...this is prerequisite to demonstrate expected behaviour of the work-Function
2023-10-25 23:40:47 +02:00
7da88b772f Scheduler: setup to verify the work-Function
...first steps to get anything to run with the Scheduler constructed thus far
...can now
 - enqueue
 - getWork -> invoke
2023-10-25 17:31:32 +02:00
a180d38ed9 Scheduler: integrate capacity handling with work-Function
...this integration becomes more and more challenging
...the high degree of inter-correlation between the scheduler components is concerning
2023-10-25 05:11:10 +02:00
d6c859fd3a Scheduler: implement and document capacity redirection 2023-10-25 02:13:18 +02:00
1d5b8c3e9c Scheduler: implement and verify random reshuffling of capacity
...using the current time itself as source for randomisation;
the test indicates this yields a smooth and even distribution.
2023-10-24 04:59:49 +02:00
3eaf623e98 Scheduler: develop scheme for capacity redirection
...to make that abundantly clear: we do not aim at precision timing,
rather the goal is to redistribute capacity currently not usable...

Basically we're telling the worker "nothing to do right now, sorry,
but check back in <timespan> because I may need you then"
2023-10-24 00:56:24 +02:00
08c13ed6fe Scheduler: consider wiring of Load-Controller
...and general questions of component design and coupling.
Decided to go for explicit configuration points by functor.
2023-10-23 21:51:16 +02:00
69fb77246e Scheduler: implement capacity redistribution scheme
wow... that was conceptually challenging, yet dead easy to implement
2023-10-23 18:48:02 +02:00
6ccb6540e6 Scheduler: implement the tended-next mark
...as KISS solution to put aside the next free capacity
whenever a new time point appears at scheduler head
2023-10-23 17:02:44 +02:00
84ca2460c1 Scheduler: fundamentals of capacity classification
Workers asking for the next task are classified as belonging
to some fraction of the free capacity, based on the distance
to the closest next Activity known to the scheduler
2023-10-23 04:07:38 +02:00
b61ca94ee5 Scheduler: rectify λ-post API
...to bring it more in line with all the other calls dealing with Activity*
...allows also to harmonise the ActivityLang::dispatchChain()
...and to compose the calls in Scheduler directly

NOTE: there is a twist: our string-formatting helper did not render
custom string conversions for objects passed as pointer. This was a
long standing problem, caused by ambiguous templates overloads;
now I've attempted to solve it one level more down, in util::StringConv.
This solution may turn out brittle, since we need to exclude any direct
string conversion, most notably the ones for C-Strings (const char*)

In case this solution turns out unsustainable, please feel free
to revert this API change, and return to passing Activity& in λ-post,
because in the end this is cosmetics.
2023-10-23 01:48:46 +02:00
a21057bdf2 Scheduler: control structure for the worker-functor 2023-10-22 23:25:35 +02:00
e5638119f5 Scheduler: devise scheme for load control
- organise by principles rather than implementing a mechanism
- keep the first version simple yet flexible
- conduct empiric research under synthetic load

Basic scheme:
- tend for next
- classify free capacity
- scattered targeted wait
2023-10-22 16:45:13 +02:00
ccf970eaee Scheduler: clarify redundant λ-post param
The signature for the »post« operation includes the ExecutionCtx itself,
which is obviously redundant, given that this operation is ''part of this context.''

However, for mock-implementation of the ExecutionCtx for unit testing,
the form of the implementation was deliberately kept unspecified, allowing
to use functor objects, which can be instrumented later. Yet a functor
stored as member has typically no access to the "this"-ptr...
2023-10-22 01:56:22 +02:00
d67c62b02f Scheduler: solve difficulties with member function signature
The approach to provide the ExecutionCtx seems to work out well;
after some investigation I found a solution how to code a generic
signature-check for "any kind of function-like member"...

(the trick is to pass a pointer or member-pointer, which happens
to be syntactically the same and can be handled with our existing
function signature helper after some minor tweaks)
2023-10-22 00:42:57 +02:00
0d2d8c3413 Scheduler: providing the execution-context
The Activity-Language can be defined by abstracting away
some crucial implementation functionality as part of an generic
»ExecutionCtx«, which in the end will be provided by the Scheduler.

But how actually?
We want to avoid unnecessary indirections, and ideally we also want
a concise formulation in-code. Here I'm exploring the idea to let the
scheduler itself provide the ExecutionCtx-operations as member functions,
employing some kind of "compile-time duck-typing"

This seems to work, but breaks the poor-man's preliminary "Concept" check...
2023-10-21 03:01:27 +02:00
26b2e6f1bd Scheduler: solve the initialisation of WorkForce
Notably I wanted an entirely static and direct binding
to the internals of the Scheduler, which can be completely inlined.
The chosen solution also has the benefit of making the back-reference
to the Scheduler explicitly visible to the reader. This is relevant,
since the Config-Subobject is *copied* into each Worker instance.
2023-10-20 18:24:50 +02:00
74c97614b3 Scheduler: component wiring
The »Scheduler Service« will be assembled
from the components developed during the last months
- Layer-1
- Layer-2
- Activity-Language
- Block-Flow
- Work-Force
2023-10-20 04:36:07 +02:00
9db341bd8b Scheduler: plan for integration
identified three distinct tasks
- build the external API
- establish component integration
- performance testing
2023-10-20 00:59:50 +02:00
9ce3ad3d72 Scheduler: Layer-2 complete and tested (see #1326)
* the implementation logic of the Scheduler is essentially complete now
 * all functionality necessary for the worker-function has been demonstrated

As next step, the »Scheduler Service« can be assembled from the two
Implementation Layers, the Activity-Language and the `BlockFlow` allocator
This should then be verified by a multi-threaded integration test...
2023-10-19 01:49:08 +02:00
10a2c6908c Scheduler: Layer-2 integration scenario complete
could even rig the diagnostic Execution-Ctx
to drop the GroomingToken at the point when switching to work-mode
2023-10-18 23:02:29 +02:00
c2ddaed28e Scheduler: draft scenario for Layer-2 integration test
Idea: re-use the scenario and instrumentation from
SchedulerActivity_test::scenario_RenderJob()
2023-10-18 18:10:10 +02:00
ee09a2eff2 Scheduler: completed implementation of Layer-2
...some further checks
...one integration test case needs to be written
2023-10-18 17:29:41 +02:00
93fcebb331 Scheduler: implement and verify postDispatch 2023-10-18 16:39:08 +02:00
666546856f Scheduler: design the core API operation - postDispatch
This central operation sits at a crossroad and is used
- from external clients to fed new work to the Scheduler
- from Workers to engage into execution of the next Activity
- recursively from the execution of an Activity-chain

From these requirements the semantics of behaviour can be derived
regarding the GroomingToken and the result values, which indicate
when follow-up work should be processed
2023-10-18 15:50:11 +02:00
55967cd649 Scheduler: work retrieval implementation
- simple approach, delegating to Layer-1
- deliberately no error handling
- GroomingToken not dropped
2023-10-18 04:18:01 +02:00
b57503fb97 Scheduler: define expected behaviour for work retrieval
still not quite sure how to implement it,
but working down from first principles to define test scenarios first...
2023-10-18 02:59:58 +02:00
aa60869082 Scheduler: decision logic for actual dispatch of activities 2023-10-18 01:38:58 +02:00
fa391d1267 Scheduler: torture test the thread access logic
Ensure the GroomingToken mechanism indeed creates an
exclusive section protected against concurrent corruption:

Use a without / with-protection test and verify
the results are exact vs. grossly broken
2023-10-17 21:35:37 +02:00
1223772f14 Scheduler: implement thread access logic
T thread holding the »Grooming Token" is permitted to
manipulate scheduler internals and thus also to define new
activities; this logic is implemented as an Atomic lock,
based on the current thread's ID.
2023-10-17 20:37:32 +02:00
862933e809 Scheduler: define API for Layer-2
Notably both Layers are conceived as functionality providers;
only at Scheduler top-Level will functionality be combined with
external dependencies to create the actual service.
2023-10-17 19:20:53 +02:00
0431a14584 Scheduler: Layer-1 complete and tested 2023-10-17 04:35:58 +02:00
430f1af4c5 Scheduler: define water-level for prioritisation 2023-10-17 03:38:28 +02:00
152413589c Scheduler: clarify role of the Time parameter
At first sight, this seems confusing; there is a time window,
there is sometimes a `when` parameter, and mostly a `now` parameter
is passed through the activation chain.

However, taking the operational semantics into account, the existing
definitions seem to be (mostly) adequate already: The scheduler is
assumed to activate a chain only ''when'' the defined start time is reached.
2023-10-17 03:04:19 +02:00
c76e5488bd Scheduler: plot steps towards integration
(1) SchedulerInvocation_test
    »Layer-1« : Queue operation

(2) SchedulerCommutator_test
    »Layer-2« : Activity execution

(3) SchedulerUsage_test
    Component End-to-End
2023-10-16 23:57:22 +02:00
3af6a54219 Library/Application: complete technology switch (closes #1279)
As follow-up to the rework of thread-handling, likewise also
the implementation base for locking was switched over from direct
usage of POSIX primitives to the portable wrappers available in
the C++ standard library. All usages have been reviewed and
modernised to prefer λ-functions where possible.

With this series of changes, the old threadpool implementation
and a lot of further low-level support facilities are not used
any more and can be dismantled. Due to the integration efforts
spurred by the »Playback Vertical Slice«, several questions of
architecture could be decided over the last months. The design
of the Scheduler and Engine turned out different than previously
anticipated; notably the Scheduler now covers a wider array of
functionality, including some asynchronous messaging. This has
ramifications for the organisation of work tasks and threads,
and leads to a more deterministic memory management. Resource
management will be done on a higher level, partially superseding
some of the concepts from the early phase of the Lumiera project.
2023-10-16 01:44:04 +02:00
685be1b039 Library/Application: consolidate Monitor API and usage
This is Step-2 : change the API towards application

Notably all invocation variants to support member functions
or a reference to bool flags are retracted, since today a
λ-binding directly at usage site tends to be more readable.

The function names are harmonised with the C++ standard and
emergency shutdown in the Subsystem-Runner is rationalised.

The old thread-wrapper test is repurposed to demonstrate
the effectiveness of monitor based locking.
2023-10-15 20:42:55 +02:00
73737f2aee Library/Application: consolidate Monitor implementation
After the fundamental switch from POSIX to the C++14 wrappers
the existing implementation of the Monitor can now be drastically condensed,
removing several layers of indirection. Moreover, all signatures
shall be changed to blend in with the names and patterns established
by the C++ standard.

This is Step-1 : consolidate the Implementation.

(to ensure correctness, the existing API towards application code was retained)
2023-10-15 02:41:41 +02:00