You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
227 lines
8.8 KiB
227 lines
8.8 KiB
The GSL Engine
|
|
==============
|
|
|
|
The following gives an outline of the conceptual approach to
|
|
simulate flow-system behavior and carries details of the
|
|
implementation along.
|
|
|
|
Introductionary remarks:
|
|
------------------------
|
|
The GSL Engine simulates signal flow systems by mapping them
|
|
onto a network of signal processing modules which are connected
|
|
via signal streams.
|
|
The signal streams are value quantized and time discrete, where
|
|
floats are used to store the quantized values and the samples are
|
|
taken at arbitrary but equidistant points in time. Also, the
|
|
sampling periods are assumed to be synchronous for all nodes.
|
|
In the public GSL C API, engine modules are exposed as GslModule
|
|
structures, for the internal engine implementation, each
|
|
GslModule is embedded in an OpNode structure.
|
|
|
|
Node Model:
|
|
-----------
|
|
* a node has n_istreams input streams
|
|
* a node has n_jstreams joint input streams facilities, that is,
|
|
an unlimited number of ouput streams can be connected to each
|
|
"joint" input stream
|
|
* a node has n_ostreams output streams
|
|
* all streams are equally value quantized as IEEE754 floats,
|
|
usually within the range -1..1
|
|
* all streams are synchronously time discrete
|
|
* the flow-system behavior can be iteratively approximated
|
|
by calculating a node's output streams from its input streams
|
|
* since all streams are equally time discrete, n output
|
|
values for all output streams can be calculated from n input
|
|
values at all input streams of a single network
|
|
* some nodes always react delayed ("deferred" nodes) and can
|
|
guarantee that they can always produce n output values ahead
|
|
of receiving the corresponding n input values, with n>=1
|
|
* a node that has no output facilities (n_ostreams==0) is
|
|
considered a "consumer" and has to be processed // FIXME
|
|
|
|
Node Methods:
|
|
-------------
|
|
->process()
|
|
This method specifies through one of its arguments
|
|
the number of iterations the node has to perform,
|
|
and therefore the number of values that are supplied
|
|
in its stream input buffers and which have to be supplied
|
|
in its stream output buffers.
|
|
->process_deferred()
|
|
This method specifies the number of input values supplied
|
|
and the number of output values that should be supplied.
|
|
The number of input values may be smaller than the number
|
|
of output values requested, in which case the node may return
|
|
less output values than requested.
|
|
|
|
Node Relationships:
|
|
-------------------
|
|
Node B is an "input" of node A if:
|
|
* one of A's input streams is connected to one of B's output streams,
|
|
or
|
|
* node C is an "input" of A and B is an "input" of C
|
|
|
|
Processing Order:
|
|
-----------------
|
|
If node A has an input node B and A is not a deferred node, B has to
|
|
be processed prior to processing A.
|
|
|
|
Connection Cycles:
|
|
------------------
|
|
Nodes A and B "form a cycle" if A is an input to B and B is an
|
|
input to A.
|
|
|
|
Invalid Connections:
|
|
--------------------
|
|
For nodes A and B (not necessarily distinct) which form a cycle,
|
|
the connections that the cycle consists of are only valid if
|
|
the following is true:
|
|
(C is a deferred node) and
|
|
(C==A or C==B or (if C is completely disconnected, the nodes
|
|
A and B do not anymore form the cycle))
|
|
|
|
|
|
Implementation Notes
|
|
====================
|
|
* if a node is deferred, all output channels are delayed
|
|
* independent leaf nodes (nodes that have no inputs) can be
|
|
scheduled separately
|
|
* nodes contained in a cycle have to be scheduled together
|
|
|
|
Scheduling Algorithm
|
|
--------------------
|
|
To schedule a consumer and its dependency nodes, schedule_query() it:
|
|
|
|
Query and Schedule Node:
|
|
* tag current node
|
|
* ignore scheduled input nodes
|
|
* schedule_query_node on untagged input nodes, then do one of:
|
|
* schedule input node (if it has no cycles)
|
|
* resolve all input nodes cycles and then schedule
|
|
the input nodes cycle (if not self in cycle)
|
|
* take over cycle dependencies from input node
|
|
* a tagged node is added to the precondition list (opens new cycle)
|
|
* own leaf level is MAX() of input node leaf-levels + 1
|
|
* untag node
|
|
|
|
Resolving Cycles:
|
|
* eliminate child from precondition list, once the list
|
|
is empty the cycle is resolved
|
|
* at least one node being eliminated has to be deferred
|
|
for the cycle to be valid
|
|
|
|
Scheduling:
|
|
* nodes need to be processed in the order of leaf-level
|
|
* within leaf-levels, processing is determined by a per-node
|
|
processing costs hint (cheap, normal, expensive)
|
|
|
|
Implementation Considerations:
|
|
------------------------------
|
|
For deferred nodes, the number n specifying the amount of output
|
|
values that are produced ahead of input can be considered
|
|
mostly-fixed. that is, it's unlikely to change often and will do
|
|
so only at block boundaries.
|
|
Supporting n to be completely variable or considering it mostly
|
|
fixed has certain implications. Here're the considerations that
|
|
led to supporting a completely variable n for the implementation:
|
|
|
|
n is block-boundary fixed:
|
|
+ for complex cycles (i.e. cycles that contain other cycles,
|
|
"subcycles"), the subcycles can be scheduled separately
|
|
if the n of the subcycle is >= block_size
|
|
- if n is the only thing that changed at a block-boundary,
|
|
rescheduling the flow-graph is required in the cases
|
|
where n = old_n + x with old_n < block_size or if x < 0
|
|
- deferred nodes can not change their delay in response to
|
|
values of an input stream
|
|
|
|
n is variable for every iteration step:
|
|
+ no rescheduling is required if n changes at block-boundary
|
|
- subcycles can not be scheduled separately from their outermost
|
|
cycle
|
|
+ the delay of deferred nodes can correlate to an input stream
|
|
|
|
|
|
Threads, communication, main loops
|
|
==================================
|
|
|
|
Thread types:
|
|
* UserThread; for the scope of the engine (the functions exposed in
|
|
gslengine.h), only one user thread may execute API functions
|
|
at a time.
|
|
i.e. if more than one user thread need to call engine API
|
|
functions, the user has to take measures to avoid concurrency
|
|
in calling these functions, e.g. by using a GslMutex which is
|
|
to be locked around engine API calls.
|
|
* MasterThread; the engine, if configured accordingly,
|
|
sets up one master thread which
|
|
- processes transactions from the UserThread
|
|
- schedules processing order of engine modules
|
|
- processes single modules when required
|
|
- processes module cycles when required
|
|
- passes back processed transactions and flow jobs to the
|
|
UserThread for garbage collection
|
|
* SlaveThread; the engine can be configured to spawn slave threads which,
|
|
in addition to the master thread
|
|
- process single modules when required
|
|
- process module cycles when required
|
|
|
|
Communication at thread boundaries:
|
|
* Job transaction queue; the UserThread constructs job transactions and
|
|
enqueues them for the MasterThread. The UserThread also dequeues already
|
|
processed transactions, in order for destroy functions of modules and
|
|
accessors to only be executed within the UserThread.
|
|
Also, the UserThread can wait (block) until all pending transactions
|
|
have been processed by the MasterThread (in order to sync state with
|
|
module network contained in the engine).
|
|
* Flow job collection list; the MasterThread adds processed flow jobs into
|
|
a collection queue, the UserThread then collects the queued flow jobs
|
|
and frees them.
|
|
* Module/cycle pool; the MasterThread fills in the module/cycle pool with
|
|
modules which need to be processed. The MasterThread and the SlaveThreads
|
|
pop modules/cycles from this pool, process them, and push back processed
|
|
nodes.
|
|
* load control; // FIXME
|
|
|
|
Main loop integration:
|
|
in order to process certain engine modules only from within
|
|
the UserThread and to drive the engine even without master
|
|
or slave threads, the engine can be hooked up to a main loop
|
|
mechanism supplied by the UserThread.
|
|
The engine provides API entry points to:
|
|
- export file descriptors and timeout, suitable for main loop backends
|
|
such as poll(2)
|
|
- check whether dispatching is necessary
|
|
- dispatch outstanding work to be performed by the engine
|
|
FIXME: needs mentioning of pollfd/callback jobs
|
|
|
|
|
|
TODO:
|
|
=====
|
|
- virtualization (poke ibuffer into outbuffer) flag (needs memcpy for deferred nodes)
|
|
- flag UserThread nodes
|
|
- sample timed jobs
|
|
- need global timestamp
|
|
- need async-queues that have time-stamped jobs
|
|
- process only so much samples until a time-stamped
|
|
job needs to be processed
|
|
- self-input cycles need to be resolved in parent as well
|
|
- node-locale async timestamped jobs
|
|
- engine-init: { block(pipe-fd), check }
|
|
- sample-timed activation can offset node's block-boundary
|
|
- need complete_blocks_only flag in node classes
|
|
- cost rating: cost*n_inputs+cost*n_outputs
|
|
- multi-input(joint) streams: job_disconnect(dmod,distr,smod,sistr); (jstreams)
|
|
|
|
Jan 07 2002 Tim Janik
|
|
* cosmetic updates, flow jobs
|
|
Aug 19 2001 Tim Janik
|
|
* notes on threads, communication, main loops
|
|
Jul 29 2001 Tim Janik
|
|
* wording/spelling fixups
|
|
May 05 2001 Tim Janik
|
|
* initial writeup
|
|
|
|
LocalWords: GSL API GslModule OpNode istreams ostreams A's B's sync
|
|
LocalWords: gslengine GslMutex UserThread MasterThread SlaveThread SlaveThreads
|