From 6e7f9edf43954529a3601dfb048cb8a1280ee0f7 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Tue, 9 Apr 2024 01:51:03 +0200 Subject: [PATCH] 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. --- src/lib/stat/statistic.hpp | 6 ++ tests/vault/gear/scheduler-stress-test.cpp | 40 ++++++++-- tests/vault/gear/stress-test-rig.hpp | 17 ++++ wiki/renderengine.html | 23 +++++- wiki/thinkPad.ichthyo.mm | 90 +++++++++++++++------- 5 files changed, 138 insertions(+), 38 deletions(-) diff --git a/src/lib/stat/statistic.hpp b/src/lib/stat/statistic.hpp index b0452580c..7816b51b2 100644 --- a/src/lib/stat/statistic.hpp +++ b/src/lib/stat/statistic.hpp @@ -245,6 +245,12 @@ namespace stat{ double x; double y; double w; + + RegressionPoint (double vx, double vy, double vw=1.0) + : x{vx} + , y{vy} + , w{vw} + { } }; using RegressionData = std::vector; diff --git a/tests/vault/gear/scheduler-stress-test.cpp b/tests/vault/gear/scheduler-stress-test.cpp index 6e72f471f..6853359b7 100644 --- a/tests/vault/gear/scheduler-stress-test.cpp +++ b/tests/vault/gear/scheduler-stress-test.cpp @@ -390,16 +390,39 @@ namespace test { cpuLoad.calibrate(); //////////////////////////////////////////////////////////////////TODO for development only MARK_TEST_FUN -// TestChainLoad testLoad{64}; -// testLoad.configure_isolated_nodes() -// .buildTopology() + TestChainLoad testLoad{50}; + testLoad.configure_isolated_nodes() + .buildTopology() // .printTopologyDOT() -// .printTopologyStatistics(); + .printTopologyStatistics(); + { + + TRANSIENTLY(work::Config::COMPUTATION_CAPACITY) = 4; + BlockFlowAlloc bFlow; + EngineObserver watch; + Scheduler scheduler{bFlow, watch}; + + auto set1 = testLoad.setupSchedule(scheduler) + .withLevelDuration(200us) + .withJobDeadline(100ms) + .withUpfrontPlanning() + .withLoadTimeBase(2ms) + .withInstrumentation(); + double runTime = set1.launch_and_wait(); + auto stat = set1.getInvocationStatistic(); +cout << "time="< + auto + linearRegression (Column const& x, Column const& y) + { + lib::stat::RegressionData points; + size_t cnt = min (x.data.size(), y.data.size()); + points.reserve (cnt); + for (size_t i=0; i < cnt; ++i) + points.emplace_back (x.data[i], y.data[i]); + return lib::stat::computeLinearRegression (points); + } + /** * Mix-in for setup of a #ParameterRange evaluation to watch * the processing of a single load peak, using the number of diff --git a/wiki/renderengine.html b/wiki/renderengine.html index 80e9eae4a..144645c96 100644 --- a/wiki/renderengine.html +++ b/wiki/renderengine.html @@ -7174,7 +7174,7 @@ Later on we expect a distinct __query subsystem__ to emerge, presumably embeddin &rarr; QuantiserImpl -
+
//Invoke and control the dependency and time based execution of  [[render jobs|RenderJob]]//
 The Scheduler acts as the central hub in the implementation of the RenderEngine and coordinates the //processing resources// of the application. Regarding architecture, the Scheduler is located in the Vault-Layer and //running// the Scheduler is equivalent to activating the »Vault Subsystem«. An EngineFaçade acts as entrance point, providing high-level render services to other parts of the application: [[render jobs|RenderJob]] can be activated under various timing and dependency constraints. Internally, the implementation is organised into two layers:
 ;Layer-2: Coordination
@@ -7213,9 +7213,22 @@ The Scheduler is now considered an implementation-level facility with an interfa
 &rarr; [[Memory|SchedulerMemory]]
 &rarr; [[Workers|SchedulerWorker]]
 &rarr; [[Internals|SchedulerProcessing]]
+&rarr; [[Behaviour|SchedulerBehaviour]]
 &rarr; [[Testing|SchedulerTest]]
 
+
+
//Characteristic behaviour traits of the [[Scheduler]] implementation//
+The design of the scheduler was chosen to fulfil some fundamental requirements..
+* flexibility to accommodate a wide array of processing patterns
+* direct integration of some notion of //dependency//
+* ability to be re-triggered by external events
+* reactive, but not over-reactive response to load peaks
+* ability to withstand extended periods of excessive overload
+* roughly precise timing with a margin of ≈ ''5''ms
+The above list immediately indicates that this scheduler implementation is not oriented towards high throughput or extremely low latency, and thus can be expected to exhibit some //traits of response and behaviour.// These traits were confirmed and further investigated in the efforts for [[stress testing|SchedulerTest]] of the new Scheduler implementation. {{red{As of 4/2024}}} it remains to be seen, if the characteristics of the chosen approach are beneficial or even detrimental to the emerging actual usage -- it may well turn out that some adjustments must be made or even a complete rewrite of the Scheduler may be necessary.
+
+
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.
@@ -7304,8 +7317,8 @@ The primary scaling effects exploited to achieve this level of performance are t
 &rarr; [[Scheduler performance testing|SchedulerTest]]
 
-
-
At first sight, the internals of [[Activity|RenderActivity]] processing may seem overwhelmingly complex -- especially since there is no active »processing loop« which might serve as a starting point for the understanding. It is thus necessary to restate the working mode of the Scheduler: it is an //accounting and direction service// for the //active// [[render workers|SchedulerWorker]]. Any processing happens stochastically and is driven by events
+
+
At first sight, the internals of [[Activity|RenderActivity]] processing may seem overwhelmingly complex -- especially since there is no active »processing loop« which might serve as a starting point for the understanding. It is thus necessary to restate the working mode of the Scheduler: it is an //accounting and direction service// for the //active// [[render workers|SchedulerWorker]]. Any processing happens stochastically and is driven by various kinds of events --
 * a //worker// becoming ready to perform further tasks
 * an external //IO event// {{red{12/23 only planned yet}}}
 * a //planning job// to add new elements to the schedule
@@ -7327,7 +7340,7 @@ The last point highlights a //circular structure:// the planning job itself was
 The way other parts of the system are built, requires us to obtain a guaranteed knowledge of some job's termination. It is possible to obtain that knowledge with some limited delay, but it nees to be absoultely reliable (violations leading to segfault). The requirements stated above assume this can be achieved through //jobs with guaranteed execution.// Alternatively we could consider installing specific callbacks -- in this case the scheduler itself has to guarantee the invocation of these callbacks, even if the corresponding job fails or is never invoked. It doesn't seem there is any other option.
 
-
+
With the Scheduler testing effort [[#1344|https://issues.lumiera.org/ticket/1344]], several goals are pursued
 * by exposing the new scheduler implementation to excessive overload, its robustness can be assessed and defects can be spotted
 * with the help of a systematic, calibrated load, characteristic performance limits and breaking points can be established
@@ -7363,6 +7376,8 @@ The point of departure for any stress testing is to show that the subject is res
 A method to determine such a »''breaking point''« in a systematic way relies on the //synthetic calculation load// mentioned above, the {{{TestChainLoad}}}. Using a simplified model of expected computation expense, a timing grid for scheduling can be established, which is then //scaled and condensed// to provoke loss of control in the Scheduler -- a condition that can be determined by statistical observation: since the process of scheduling contains an element of //essential randomness,// persistent overload will be indicated by an increasing variance of the overall runtime, and a departure from the nominal runtime of the executed schedule. This consideration leads to a formalised condition, which can be used to control a //binary search// to find the breaking point in terms of a //critical stress factor.//
 
 Observing this breaking point in correlation with various load patterns will unveil performance characteristics and weak spots of the implementation.
+&rarr; [[Scheduler behaviour traits|SchedulerBehaviour]]
+
 
diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 1d33c8510..869fd4565 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -111018,7 +111018,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + @@ -111196,7 +111197,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -111941,7 +111942,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -112048,25 +112049,6 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - - - - - - - - -

- ...und was dieser Kontroll-Parameter tatsächlich ist, wird allein durch das Setup festgelegt — denn dies erzeugt sowohl jedesmal eine neue Topologie -

- -
-
- - -
@@ -114850,8 +114832,7 @@ std::cout << tmpl.render({"what", "World"}) << s Denn es gibt stets einige wenige ganz dramatische Ausreißer, die sonst die Skala so verschieben würden, daß man die eigentlichen Meßwerte nicht mehr sieht; ich müßte also eigens wieder Anpassungs-Code schreiben, und mir eine Heuristik einfallen lassen, um die Skala zu kappen — unnötiger Aufwand für eine nebenbei mit aufgenommene Größe

- - +
@@ -114863,8 +114844,7 @@ std::cout << tmpl.render({"what", "World"}) << s ...denn der Umstand, daß der Overhead / Job weitgehend konstant ist, zeigt, daß wir hier einen separaten Setup-Effekt haben, vermutlich nämlich vor allem die Job-Planungs-Zeit. Da wir demgegenüber in der aktiven Phase sehr gut linear sind, kann man diesen zusätzlichen Overhead zunächst mal als Artefakt der Meßanordnung beiseite lassen (und später dann mal eigens untersuchen)

- -
+
@@ -115161,6 +115141,26 @@ std::cout << tmpl.render({"what", "World"}) << s
+ + + + + + + + + + + +

+ ...und was dieser Kontroll-Parameter tatsächlich ist, wird allein durch das Setup festgelegt — denn dies erzeugt sowohl jedesmal eine neue Topologie +

+ +
+
+ + +
@@ -115308,7 +115308,7 @@ std::cout << tmpl.render({"what", "World"}) << s - + @@ -115413,6 +115413,37 @@ std::cout << tmpl.render({"what", "World"}) << s + + + + + + + + + + + + + + + +

+ ...denn er ist stark von der Hardware abhängig und i.d.R wenig variierbar, und oft gibt es noch weitere, interne Beschränkungen (wie z.B. Hyperthreading, das eben doch nicht komplett transparent ist und mit Cache und Pipelining im Prozessor wechselwirkt) +

+ + +
+
+
+
+ + + + + + + @@ -115522,7 +115553,7 @@ std::cout << tmpl.render({"what", "World"}) << s - + @@ -116397,6 +116428,9 @@ std::cout << tmpl.render({"what", "World"}) << s + + +