Commit graph

2687 commits

Author SHA1 Message Date
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
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
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
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
7a22e7f987 Test: helper for transitory manipulations
Use a simple destructor-trick to set up a concise notation
for temporarily manipulating a value for testing.
The manipulation will automatically be undone
when leaving scope
2023-11-08 19:27:08 +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