diff --git a/src/vault/gear/scheduler.hpp b/src/vault/gear/scheduler.hpp index 59edf4bb1..85ee5ea30 100644 --- a/src/vault/gear/scheduler.hpp +++ b/src/vault/gear/scheduler.hpp @@ -244,6 +244,8 @@ namespace gear { }; + + /** * @remark when due, the scheduled Activities are performed within the * [Activity-Language execution environment](\ref ActivityLang::dispatchChain()); diff --git a/tests/vault/gear/scheduler-service-test.cpp b/tests/vault/gear/scheduler-service-test.cpp index 83c4375f1..8e00eb9ed 100644 --- a/tests/vault/gear/scheduler-service-test.cpp +++ b/tests/vault/gear/scheduler-service-test.cpp @@ -100,6 +100,21 @@ namespace test { * - this implies we can show timing-delay effects in the millisecond range * - demonstrated behaviour * + an Activity already due will be dispatched immediately by post() + * + an Activity due at the point when invoking the work-function is dispatched + * + while queue is empty, the work-function returns immediately, indicating sleep + * + invoking the work-function when there is still some time span up to the next + * planned Activity will enter a targeted sleep, returning shortly after the + * next schedule. Entering then again will cause dispatch of that activity. + * + if the work-function dispatches an Activity while the next entry is planned + * for some time ahead, the work-function will likewise go into a targeted + * sleep and only return at or shortly after that next planned time entry + * + after dispatching an Activity in a situation with no follow-up work, + * the work-function inserts a targeted sleep of random duration, + * to re-shuffle the rhythm of sleep cycles + * + when the next planned Activity has already be »tended for« (by placing + * another worker into a targeted sleep), further workers entering the + * work-function will be re-targeted by a random sleep to focus capacity + * into a time zone behind the next entry. * @note Invoke the Activity probe itself can take 50..150µs, due to the EventLog, * which is not meant to be used in performance critical paths but only for tests, * because it performs lots of heap allocations and string operations. Moreover, @@ -158,6 +173,7 @@ namespace test { cout << "pullWork() on empty queue..."< up-front delay"< follow-up delay"< 900); // yet this thread was afterwards kept in sleep to await the next one CHECK (activity::PASS == res); // instruction to re-invoke immediately CHECK (not scheduler.empty()); // since there is still work in the queue @@ -226,13 +234,32 @@ SHOW_EXPR(scheduler.empty()) pullWork(); // re-invoke immediately as instructed CHECK (wasInvoked(start)); // Result: also the next Activity has been dispatched CHECK (slip_us < 400); // not much slip - CHECK (slip_us < 20000); // ...and the post-delay is used to re-shuffle the sleep cycle as usual + CHECK (delay_us < 20200); // ...and the post-delay is used to re-shuffle the sleep cycle as usual CHECK (activity::PASS == res); // since queue is empty, we will call back once... CHECK (scheduler.empty()); pullWork(); CHECK (activity::WAIT == res); // and then go to sleep. - cout << detector.showLog()< re-target capacity"< -
+
//The operational logic of Activity execution is the concrete service provided by the [[Scheduler]] to implement interwoven [[render Activities|RenderActivity]] and [[Job  execution|RenderJob]].//
 * logically, each {{{Activity}}} record represents a //verb// to describe some act performed by »the render process«
 * the {{{ActivityLang}}} provides a //builder notation// to build „sentences of activities“ and it sets the framework for //execution// of Activities
@@ -7049,7 +7049,7 @@ __see also__
 * the ''Scheduler Layer-1'' ({{{SchedulerInvocation}}}) provides the low-level coordination and invocation mechanics to launch [[render Jobs|RenderJob]].
 
 !Framework for Activity execution
-The individual {{{Activity}}} records serve as atomic execution elements; an Activity can be invoked once, either by time-bound trigger in the Scheduler's priority queue, or by receiving an activation message (directly when in //management mode,// else indirectly through the invocation queue). The data structure of the {{{Activity}}} record (&rarr; [[description|RenderActivity]]) is maintained by the [[»block flow« memory allocation scheme|SchedulerMemory]] and can be considered stable and available (within the logical limits of its definition, which means until the overarching deadline has passed). The ''activation'' of an Activity causes the invocation of a hard-wired execution logic, taking into account the //type field// of the actual {{{Activity}}} record to be »performed«. This hard-wired logic however can be differentiated into a //generic// part (implemented directly in {{{class Activity}}}) and a //contextual// part, which is indirected through a ''λ-binding'', passed as ''execution context'', yet actually implemented by functions of ''Scheduler Layer-2''.
+The individual {{{Activity}}} records serve as atomic execution elements; an Activity shall be invoked once, either by time-bound trigger in the Scheduler's priority queue, or by receiving an activation message (directly when in //management mode,// else indirectly through the invocation queue). The data structure of the {{{Activity}}} record (&rarr; [[description|RenderActivity]]) is maintained by the [[»block flow« memory allocation scheme|SchedulerMemory]] and can be considered stable and available (within the logical limits of its definition, which means until the overarching deadline has passed). The ''activation'' of an Activity causes the invocation of a hard-wired execution logic, taking into account the //type field// of the actual {{{Activity}}} record to be »performed«. This hard-wired logic however can be differentiated into a //generic// part (implemented directly in {{{class Activity}}}) and a //contextual// part, which is indirected through a ''λ-binding'', passed as ''execution context'', yet actually implemented by functions of ''Scheduler Layer-2''.
 !!!execution patterns
 Since the render engine can be considered performance critical, only a fixed set of //operational patterns// is supported, implemented with a minimum of indirections and thus with limited configurability. It seems indicated to confine the scope of this operational logic to a finite low-level horizon, assuming that //all relevant high-level render activities// can actually be //expressed in terms of these fundamental patterns,// in combination with an opaque JobFunctor.
 ;Frame Render Job
@@ -7201,7 +7201,7 @@ The Scheduler is now considered an implementation-level facility with an interfa
 &rarr; [[Workers|SchedulerWorker]]
 
-
+
The scheduling mechanism //requires active control of work parameters to achieve good performance on average.//
 In a nutshell, the scheduler arranges planned [[render activities|RenderActivity]] onto a time axis -- and is complemented by an [[active »work force«|SchedulerWorker]] to //pull and retrieve// the most urgent next task when free processing capacity becomes available. This arrangement shifts focus from the //management of tasks// towards the //management of capacity// -- which seems more adequate, given that capacity is scarce while tasks are abundant, yet limited in size and processed atomically.
 
@@ -7217,15 +7217,30 @@ Notably the use of special purpose hardware or special OS privileges is //not re
 
 This fundamental strategy translates into the goal to provide each worker with the next piece of work as soon as the thread returns from previous work. Putting this scheme into practice however faces major obstacles stemming from //work granularity// and //timing patterns.// Threads will call for work at essentially random time points, and in no way related to the calculation schedule laid out in advance based on reasoning about dependencies, resource allocation and deadlines. This is a problem hard to overcome on the level of individual operationality. And thus, in accordance to the goals set out above, demanding to aim at optimal use of recurses //on average// -- it is indicated to change to a //statistical perspective// and to interpret the worker call as an //capacity event.//
 !!!The statistical flow of capacity
-By employing this changed perspective, the ongoing train of worker calls transliterates into a flow of capacity. And this capacity can be grouped and classified. The optimal situation -- which should be achieved on average -- is to be able to provide the next piece of work //immediately// for each worker calling in. On the other hand, target times set out for scheduling should be observed, and thus this optimum state can be characterised as »being slightly overdue«. Ideally, capacity should thus flow-in //slightly behind schedule.// On an operational level, since „capacity flow“ is something generated stochastically, the desired state translates into adjustments to re-distribute the worker callback time points into some focal time zone, located slightly behind the next known [[render activity|RenderActivity]] to tend for. (As aside, an //alternative approach// -- likewise considered for the scheduler design -- would be to pre-attribute known capacity to the next round of known scheduling events; this solution was rejected however, presumably leading to excessively fine-grained and unnecessarily costly computation, disregarding the overall probabilistic nature of the rendering process.)
+By employing this changed perspective, the ongoing train of worker calls transliterates into a flow of capacity. And this capacity can be grouped and classified. The optimal situation -- which should be achieved on average -- is to be able to provide the next piece of work //immediately// for each worker calling in. On the other hand, target times set out for scheduling should be observed, and optimum state can thus be characterised as »being slightly overdue«. Ideally, capacity should thus flow-in //slightly behind schedule.// At operational level, since „capacity flow“ is something generated stochastically, the desired state translates into adjustments to re-distribute the worker callback time points into some focal time zone, located slightly behind the next known [[render activity|RenderActivity]] to tend for. (As aside, an //alternative approach// -- likewise considered for the scheduler design -- would be to pre-attribute known capacity to the next round of known scheduling events; this solution was rejected however, presumably leading to excessively fine-grained and unnecessarily costly computation, disregarding the overall probabilistic nature of the rendering process.)
 
-Drawing upon these principles, the treatment of worker calls can be aligned to the upcoming schedule. On average, the planning of this schedule can be assumed to happen in advance with suitably safety margin (notwithstanding the fact that re-scheduling and obliteration of established schedule can happen any time, especially in response to user interaction). The next-known »head element« of the schedule will thus play a //pivotal role,// when placed into the context of the //current time of request.// On average, this head element will be the next activity to consider, and the distance-of-encounter can be taken as indicator for //current density of work.// This allows to establish a priority of concerns to consider when answering a worker pull call, thereby transitioning the capacity-at-hand into a delineated segment of the overall capacity:
+Drawing upon these principles, the treatment of worker calls can be aligned to the upcoming schedule. On average, the planning of this schedule can be assumed to happen in advance with suitable safety margin (notwithstanding the fact that re-scheduling and obliteration of established schedule can happen any time, especially in response to user interaction). The next-known »head element« of the schedule will thus play a //pivotal role,// when placed into the context of the //current time of request.// On average, this head element will be the next activity to consider, and the distance-of-encounter can be taken as indicator for //current density of work.// This allows to establish a priority of concerns to consider when answering a worker pull call, thereby transitioning the capacity-at-hand into a dedicated segment of the overall capacity:
 # ideally, activities slightly overdue can be satisfied by //active capacity//
 # spinning-wait is adequate for imminent events below scheduling latency
 # next priority to consider is a head element located more into the future
 # beyond that, capacity can be redirected into a focussed zone behind the head
 # and if the head is far away, capacity is transitioned into the reserve segment
-Worker capacity events can be further distinguished into //incoming capacity// and //outgoing capacity.// The latter is the most desirable transition, since it corresponds to a worker just returning from a computation task, while incoming capacity stems from workers calling back after a sleep period. Thus a high priority is placed on re-assigning the outgoing capacity immediately back to further active work, while for incoming capacity there is an increased preference to send it back to sleep. A worker placed into the sleeping reserve for an extended stretch of time can be considered excess capacity and will be removed from the [[work force|SchedulerWorker]]. This kind of asymmetry creates a cascading flow, and allows in the end to synthesise an average load factor, which in turn can be used to regulate the Engine on a global level. If scheduling is overloaded, increasing the slip of planned timings, more capacity might be added back when available, or the planning process should be throttled accordingly.
+Worker capacity events can be further distinguished into //incoming capacity// and //outgoing capacity.// The latter is the most desirable transition, since it corresponds to a worker just returning from a computation task, while incoming capacity stems from workers calling back after a sleep period. Thus a high priority is placed on re-assigning the outgoing capacity immediately back to further active work, while for incoming capacity there is a preference to send it back to sleep. A worker placed into the sleeping reserve for an extended stretch of time can be considered excess capacity and will be removed from the [[work force|SchedulerWorker]]. This kind of asymmetry creates a cascading flow, and allows in the end to synthesise an average load factor, which in turn can be used to regulate the Engine on a global level. If scheduling is overloaded, increasing the slip of planned timings, more capacity might be added back when available, or the planning process should be throttled accordingly.
+
+!!!Playback and processing priority
+Not all tasks are on equal footing though. Given the diverse nature of functions fulfilled by the render engine, several kinds of render processes can be distinguished:
+;Free-wheeling
+:Final quality rendering must be completed flawlessly, //whatever it takes.//
+:Yet there is no temporal limit, rather it is tantamount to use resources efficiently.
+;Time-bound
+:Media playback has to observe clear temporal constraints and is obsolete when missing deadlines.
+:Making progress when possible is more important than completing every chain of activities
+;Background
+:Various service tasks can be processed by the render engine when there is excess capacity to use.
+For the likes of UI audio visualisation or preview caching there is typically no deadline at all, as long as they do not jeopardise important work
+These distinct »processing classes« seem to impose almost contradictory calculation patterns and goals, hard to fit into a single organisational scheme. Yet the //statistical view angle// outlined above offers a way to reconcile such conflicting trends -- assuming that there is a pre-selection on a higher organisational level to ensure the overall processing load can be handled with the given resources. Indeed it is pointless even to start three real-time playback processes in a situation where it is blatantly clear that the system can handle only two. Yet when a rough guess of expected load indicates that a real-time playback will only consume half of the system's processing capacity, even overprovisioning of background processes to some degree is feasible, as long as the real-time playback gets the necessary headroom. And all these different processing patterns can be handled within the same scheduling framework, as long as they can be translated into a //probability to acquire the resources.//
+
+A task placed into the scheduling timeline basically represents some //»cross-section of probability«.// If work capacity happens to „land“ into its definition realm and no further overdue task is scheduled before, the task can use the available capacity. Yet when the deadline of the task has passed without any free capacity „hitting“ its scope, its chances are wasted and the scheduler will discard the entry. And by exploiting these relationships, it is possible to craft a scheme of time slot allocation to translate an abstract processing priority into a probabilistic access to some fraction of capacity. Even more so, since it is possible to place multiple instances of the same task into the scheduling timeline, relying on the [[»Gate« mechanism|RenderOperationLogic]] of the [[Activity-Language|RenderActivity]], to spread out a small level of background processing probability over an extended period of time.
 
diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index b7df16897..34d40fe1b 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -82420,8 +82420,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -82722,9 +82722,10 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - + + @@ -83169,8 +83170,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- +
@@ -88169,11 +88170,12 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + - + + @@ -88187,35 +88189,37 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + - + - + - - - + + + - - + + + + - + @@ -88263,7 +88267,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -88279,7 +88283,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -88568,8 +88572,14 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ +
+
+ + + @@ -88594,14 +88604,15 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + - - + + @@ -88621,6 +88632,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + +
@@ -91744,7 +91758,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -91815,10 +91829,10 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - + @@ -91829,6 +91843,195 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + +

+ Gruß vom Vollmond in Pullach (noch nicht ganz voll, morgen ist Mondfinsternis), der geht das Tal entlang mit +

+ + +
+ + + + + + + + + + + + + + + + + + +
+ + + + + + +

+ Priorität bestimmt die Wahrscheinlichkeit, +

+

+ Kapazität zufällig zu erlangen +

+ + +
+ + + + + + + + +

+ Grundsätzlich darf ein Render-Prozeß nur bei vorhandener Kapazität zugelassen werden. Aber die Anwendung dieses Prinzips hat Abstufungen, die sich aus der Art des Prozesses bestimmen. +

+
    +
  • + ein whatever it takes - Render bekommt die gesamte Kapazität. Punkt. (alles andere würde ihm schaden) +
  • +
  • + ein realtime-Playback muß im Stande sein, seine Termine zu halten; unter dieser Maßgabe muß er Vorfahrt bekommen, kann aber durchaus noch Raum freilassen, wenn er nicht außerdem auch noch die Leistungsfähigkeit des Systems komplett ausschöpft +
  • +
  • + demgegenüber kann man Background-Prozesse dem Wettbewerb überlassen, und dabei sogar noch verschiedene Level definieren. Es sind nur zwei Bedingungen einzuhalten: +
  • +
      +
    • + Activities müssen feingranular sein, damit sie wirklich nur die Reste verbrauchen +
    • +
    • + es muß dafür gesorgt werden, daß der Prozeß tatsächlich fortschreitet +
    • +
    +
+ + +
+
+ + + + + + +

+ die aktuelle Situation bestimmt darüber, wie Kapazität zugeführt wird; aber der Querschnitt bestimmt, wie gute Chancen ein konkreter Task hat, davon etwas abzubekommen +

+ + +
+ + + + + + +

+ Ein frei stehender Task bekommt durch den tend-Next-Mechanismus auch schon sehr viel früher die nächste frei werdende Kapazität zugeordnet; solange aber in der einfachen zeitlichen Ordnung noch etwas vor ihm steht (selbst wenn überfällig), dann zieht dieses die Priorität auf sich +

+ + +
+
+ + + + + + +

+ sobald er aber frei steht, bestimmt seine Länge die Priorität +

+ + +
+ + + + + +

+ Und zwar durch das Ende (die Deadline), nach deren Überstreichen der Task effektiv unwirksam ist und im Vorrübergehen entnommen und verworfen wird. Wenn nun verschiedene Tasks jeweils in der Länge beschränkt sind, dann fällt ihnen diejenige Kapazität zu, die zufällig in ihrem Wirkradius „landet“ +

+ + +
+
+ + + + + + +

+ Da sind zunächst die Gates von Relevanz. Ein noch geschlossenes Gate kann einen Task nach hinten schieben und damit andere Tasks aufdecken. Ein getriggertes und endgültig geschlossenes Gate nimmt den Task aus der Konkurrenz komplett heraus. Und außerdem werden Tasks auch noch über eine Prozeß/Kontext-ID gekennzeichnet, wodurch eine Revision und Aktualisierung eines gesamten Planungsvorgangs möglich wird. +

+ + +
+
+
+ + + + + + +

+ ...das bedeutet, wenn wir einmal durch das Gate gegangen sind, ist es endgültig geschlossen. Deshalb können gewissermaßen mehrere »Instanzen« eingeplant werden, denn das Wegräumen von Müll ist in einer Priority-Queue relativ effizient, O(logₙ), und läuft bei uns im einstelligen µs-Bereich pro Einzelschritt +

+ + +
+
+ + + + + + +

+ Ein Task der nur Restkapazität bekommen soll, darf niemals am Anfang des Fensters stehen, und auch möglichst nicht ganz am Ende. Je kürzer und je mehr in der Mitte, desto geringer seine Chancen. Ein Task der in jedem Fall Kapazität bekommen soll, muß nur hinreichend weit nach hinten reichen (aber unter der Einschränkung, unseren Epochen-basierten BlockFlow nicht zu überlasten). Oder er muß hinreichend oft wiederholt werden. Es wird also ein generelles Segment-Schema etabliert, und in diesem gibt es vorgefertigte »Slots«. Gemäß übergeordnete Kapazitätsplanung werden diese Slots in Anspruch genommen. Wenn wir beispielsweise eine Restkapazität 10-fach überprovisionieren, dann bedeutet das, jeden einzelnen Task (ggfs. mit Zeitabstand) 10+x-mal einzuplanen, wobei x einen empirisch bestimmten safety-margin darstellt, um die tatsächlichen Fluktuationen der Kapazität abzufedern... +

+ + +
+
+ + + + + + +

+ dadurch übersetzen wir multidimensionale Zusammenhänge +

+

+ von der übergeordneten Planungsebene +

+

+ in ein low-Level-Ausführungsschema +

+ + +
+ +
@@ -91866,7 +92069,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- +