diff --git a/tests/vault/gear/scheduler-stress-test.cpp b/tests/vault/gear/scheduler-stress-test.cpp index e86f5db5f..9c9f59cbe 100644 --- a/tests/vault/gear/scheduler-stress-test.cpp +++ b/tests/vault/gear/scheduler-stress-test.cpp @@ -355,7 +355,8 @@ namespace test { auto testLoad() { return TestChainLoad<>{64}.configureShape_chain_loadBursts(); } }; - auto [stress,delta,time] = StressRig::with().searchBreakingPoint(); + auto [stress,delta,time] = StressRig::with() + .perform(); CHECK (delta > 2.5); CHECK (1.15 > stress and stress > 0.9); } @@ -429,7 +430,8 @@ SHOW_EXPR(loadMicros) return testLoad; } }; - auto [stress,delta,time] = StressRig::with().searchBreakingPoint(); + auto [stress,delta,time] = StressRig::with() + .perform(); SHOW_EXPR(stress) SHOW_EXPR(delta) SHOW_EXPR(time) diff --git a/tests/vault/gear/stress-test-rig.hpp b/tests/vault/gear/stress-test-rig.hpp index 3a2ef78b5..715909f54 100644 --- a/tests/vault/gear/stress-test-rig.hpp +++ b/tests/vault/gear/stress-test-rig.hpp @@ -64,6 +64,13 @@ ** which is 2 times the basic failure indicator. ** ** ## Observation tools + ** As a complement to the bench::BreakingPoint tool, another tool is provided to + ** run a specific Scheduler setup while varying a single control parameter within + ** defined limits. This produces a set of (x,y) data, which can be used to search + ** for correlations or build a linear regression model to describe the Scheduler's + ** behaviour as function of the control parameter. The typical use case would be + ** to use the input length (number of Jobs) as control parameter, leading to a + ** model for the Scheduler's expense. ** ** @see TestChainLoad_test ** @see SchedulerStress_test @@ -132,18 +139,106 @@ namespace test { } - namespace stress_test_rig { + + + /** configurable template framework for running Scheduler Stress tests */ + class StressRig + : util::NonCopyable + { + + public: + /***********************************************************************//** + * Entrance Point: build a stress test measurement setup using a dedicated + * \a TOOL class, takes the configuration \a CONF as template parameter + * and which is assumed to inherit (indirectly) from StressRig. + * @tparam CONF specialised subclass of StressRig with customisation + * @return a builder to configure and then launch the actual test + */ + template + static auto + with() + { + return Launcher{}; + } + + + /* ======= default configuration (inherited) ======= */ + + using usec = std::chrono::microseconds; + + usec LOAD_BASE = 500us; + usec BASE_EXPENSE = 0us; + bool SCHED_NOTIFY = true; + bool SCHED_DEPENDS = false; + uint CONCURRENCY = work::Config::getDefaultComputationCapacity(); + bool INSTRUMENTATION = true; + double EPSILON = 0.01; ///< error bound to abort binary search + double UPPER_STRESS = 0.6; ///< starting point for the upper limit, likely to fail + double FAIL_LIMIT = 2.0; ///< delta-limit when to count a run as failure + double TRIGGER_FAIL = 0.55; ///< %-fact: criterion-1 failures above this rate + double TRIGGER_SDEV = FAIL_LIMIT; ///< in ms : criterion-2 standard derivation + double TRIGGER_DELTA = 2*FAIL_LIMIT; ///< in ms : criterion-3 average delta above this limit + bool showRuns = false; ///< print a line for each individual run + bool showStep = true; ///< print a line for each binary search step + bool showRes = true; ///< print result data + bool showRef = true; ///< calculate single threaded reference time + + static uint constexpr REPETITIONS{20}; + + BlockFlowAlloc bFlow{}; + EngineObserver watch{}; + Scheduler scheduler{bFlow, watch}; + + + + protected: + /** Extension point: build the computation topology for this test */ + auto + testLoad() + { + return TestChainLoad<>{64}; + } + + /** (optional) extension point: base configuration of the test ScheduleCtx */ + template + auto + testSetup (TL& testLoad) + { + return testLoad.setupSchedule(scheduler) + .withJobDeadline(100ms) + .withUpfrontPlanning(); + } + + template + struct Launcher : CONF + { + template class TOOL, typename...ARGS> + auto + perform (ARGS&& ...args) + { + return TOOL{}.perform (std::forward (args)...); + } + }; + }; + + + + + namespace bench { ///< Specialised tools to investigate scheduler performance - /** + using std::declval; + + + /**************************************************//** * Specific stress test scheme to determine the * »breaking point« where the Scheduler starts to slip */ template - class BreakingPointBench - : CONF + class BreakingPoint + : public CONF { - using TestLoad = decltype(std::declval().testLoad()); - using TestSetup = decltype(std::declval().testSetup (std::declval())); + using TestLoad = decltype(declval().testLoad()); + using TestSetup = decltype(declval().testSetup (declval())); struct Res { @@ -301,7 +396,7 @@ namespace test { * @return a tuple `[stress-factor, ∅delta, ∅run-time]` */ auto - searchBreakingPoint() + perform() { TRANSIENTLY(work::Config::COMPUTATION_CAPACITY) = CONF::CONCURRENCY; @@ -323,73 +418,43 @@ namespace test { return make_tuple (res.stressFac, res.avgDelta, res.avgTime); } }; - }//namespace stress_test_rig - - - - /** configurable template for running Scheduler Stress tests */ - class StressRig - : util::NonCopyable - { - - public: - using usec = std::chrono::microseconds; - - usec LOAD_BASE = 500us; - usec BASE_EXPENSE = 0us; - bool SCHED_NOTIFY = true; - bool SCHED_DEPENDS = false; - uint CONCURRENCY = work::Config::getDefaultComputationCapacity(); - bool INSTRUMENTATION = true; - double EPSILON = 0.01; ///< error bound to abort binary search - double UPPER_STRESS = 0.6; ///< starting point for the upper limit, likely to fail - double FAIL_LIMIT = 2.0; ///< delta-limit when to count a run as failure - double TRIGGER_FAIL = 0.55; ///< %-fact: criterion-1 failures above this rate - double TRIGGER_SDEV = FAIL_LIMIT; ///< in ms : criterion-2 standard derivation - double TRIGGER_DELTA = 2*FAIL_LIMIT; ///< in ms : criterion-3 average delta above this limit - bool showRuns = false; ///< print a line for each individual run - bool showStep = true; ///< print a line for each binary search step - bool showRes = true; ///< print result data - bool showRef = true; ///< calculate single threaded reference time - - static uint constexpr REPETITIONS{20}; - - BlockFlowAlloc bFlow{}; - EngineObserver watch{}; - Scheduler scheduler{bFlow, watch}; - - /** Extension point: build the computation topology for this test */ - auto - testLoad() - { - return TestChainLoad<>{64}; - } - - /** (optional) extension point: base configuration of the test ScheduleCtx */ - template - auto - testSetup (TL& testLoad) - { - return testLoad.setupSchedule(scheduler) - .withJobDeadline(100ms) - .withUpfrontPlanning(); - } - - /** - * Entrance Point: build a stress test measurement setup - * to determine the »breaking point« where the Scheduler is unable - * to keep up with the defined schedule. - * @tparam CONF specialised subclass of StressRig with customisation - * @return a builder to configure and then launch the actual test - */ - template - static auto - with() - { - return stress_test_rig::BreakingPointBench{}; - } - }; - - -}}} // namespace vault::gear::test + + + + + /**************************************************//** + * Specific test scheme to perform a Scheduler setup + * over a given control parameter range to determine + * correlations + */ + template + class ParameterRange + : public CONF + { + using TestLoad = decltype(declval().testLoad()); + using TestSetup = decltype(declval().testSetup (declval())); + + + + public: + /** + * Launch a measurement sequence running the Scheduler with a + * varying parameter value to investigate (x,y) correlations. + * @return ////TODO a tuple `[stress-factor, ∅delta, ∅run-time]` + */ + auto + perform() + { + TRANSIENTLY(work::Config::COMPUTATION_CAPACITY) = CONF::CONCURRENCY; + + TestLoad testLoad = CONF::testLoad().buildTopology(); + TestSetup testSetup = CONF::testSetup (testLoad); + + UNIMPLEMENTED ("parametric runs"); +// return make_tuple (res.stressFac, res.avgDelta, res.avgTime); + } + }; + // + }// namespace bench +}}}// namespace vault::gear::test #endif /*VAULT_GEAR_TEST_STRESS_TEST_RIG_H*/ diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index e2c256678..c8b3c62bb 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -140,9 +140,7 @@ - - - +

AUA: Henne oder Ei? @@ -152,9 +150,7 @@ - - - +

denn: @@ -176,9 +172,7 @@ - - - +

gemeint ist: im ctor @@ -198,9 +192,7 @@ - - - +

oder anders herum, @@ -53506,9 +53498,7 @@ - - - +

...weil es zu jedem InvocationPath @@ -53527,9 +53517,7 @@ - - - +

gemeint ist: @@ -53566,9 +53554,7 @@ - - - +

die Idee ist hier, @@ -53612,9 +53598,7 @@ - - - +

...eben! @@ -53736,9 +53720,7 @@ - - - +

vermutlich läuft es immer darauf hinaus @@ -53844,9 +53826,7 @@ - - - +

...also ist eine Instanz durchaus noch am Leben, @@ -53863,9 +53843,7 @@ - - - +

...damit die Nummer erhalten bleibt @@ -110670,7 +110648,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -110730,6 +110708,18 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + +
+ + + + + + + @@ -110965,8 +110955,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + @@ -111115,8 +111105,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + @@ -111150,9 +111140,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

erster Versuch: überhaupt keinen Graph konstruieren @@ -111182,9 +111170,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

fast alle Threads sind im Contention-wait @@ -111195,9 +111181,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

das ist zwar ganz klar geplant, aber ich jetzt lasse ich die Tests erst mal so laufen... @@ -111209,9 +111193,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

Zwar beobachte ich die Memory-Corruption dann ehr beim Einfügen der Events, aber das kann sehr wohl ein Folge-Fehler sein. Ich sehe auch anderes Verhalten, das „eigentlich gar nicht möglich ist“ @@ -111222,9 +111204,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

Hier passiert ein Vector-realloc. Wenn währenddessen der nächste Thread kommt, macht der ebenfalls ein re-Alloc und die Memory-corruption wäre garantiert. @@ -111236,9 +111216,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

Das ist ein Instrumentierungs-Hilfsmittel, und generell nur darauf ausgelegt, »richtig verwendet zu werden« @@ -111289,9 +111267,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

...waren aber geblockt, solange noch die eigentlichen Jobs mit t=0 in der Queue stehen @@ -111313,9 +111289,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

...und die Selbstreguliertung sorgt schntell dafür, daß der größte Teil der WorkForce in contention-Wait geht @@ -111331,9 +111305,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

zweiter Versuch: unconnected Nodes with weight (500µs) @@ -111355,9 +111327,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

das entspricht exakt der Concurrency @@ -111381,9 +111351,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

das ist effektiv concurrency ≡ 6 @@ -111409,13 +111377,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + - - - +

mit 400 Nodes  ⟼ real ∅ 12ms und Concurrency ≡ 7.9 @@ -111440,9 +111408,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

...weil keine Festlegung auf eine bestimmte Zahl an Cores möglich ist und auch die Werte stark statistisch schwanken, besonders bei den relativ kurzen Laufzeiten, die hier aus praktischen Gründen erforderlich sind, schon um das Test-Setup einfach zu halten @@ -111452,16 +111418,14 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + - - - +

Stichwort: Box stacking. Das ist ein NP-hartes Problem und wir lösen es hier nicht; vielmehr wird so getan, als könnte man um die durchschnittliche Concurrency elastisch stauchen. Das führt dann zum Auftreten eines weiteren »Form-Faktors«, der eben genau darin besteht, zu welchem Grad diese Annahme zu den strukturellen Beschränkugeneni im Widerspruch steht. @@ -111486,9 +111450,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

da wir die Überschreitung einer Grenze beobachten, kann bereits ein zufälliger "outlier" den Suchmechanismus in ein falsches Intervall „abbiegen lassen“. Denn eine einmal etablierte Intervall-Grenze wird grundsätzlich nicht nochmal geprüft. Ich hatte deswegen regelmäßig grob daneben liegende Ergebnisse beobachtet. Ein typisches Beispiel @@ -111532,9 +111494,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

Mehraufwand ⟹ bereits ein entsprechend kleinerer Stress-Faktor hat die gleiche Stress-Wirkung @@ -111558,9 +111518,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

...das läßt dann die nominelle Concurrency als integral-Zahl intakt @@ -111578,7 +111536,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + @@ -111638,9 +111597,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

denn das Schedule wird ja auf eine nominelle Concurrency ausgelegt @@ -111651,9 +111608,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

und diese wird ggfs aus strukturellen Gründen nicht ausgeschöpft @@ -111670,9 +111625,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

...die Berechnung läuft zwar genauso, nämlich über eine Gruppierung per Level, jedoch müssen dann nur die reinen Nodes pro Level berücksichtigt werden @@ -111683,9 +111636,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

weil die Gewichte entsprechend proportional auch in die durchschnittliche empirische Concurrency eingehen @@ -111700,9 +111651,121 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + +

+ ...denn es ist tatsächlich sowas wie ein »Form-Faktor«, ergibt sich also aus der konkreten Dependency-Struktur und der konkreten Scheduling-Situation. Umso besser, daß dieser Wert sich auch nur leicht vom »Druck« abhängig erweist; es ist also etwas Situatives, und insofern arbeitet der »Streß« relativ dazu +

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

+ ohne starke Abhängigkeiten ist durchaus elastisches Verhalten denkbar +

+ + +
+ + + +

+ Zwar ist die nun entwickelte Meßmethode durchaus elegant, wird aber zunehmend ungenau, wenn es kein klar definiertes »Brechen« mehr gibt, bzw. wenn man nur noch einen numerischen Grenzwert festlegen kann, der dann von den Dimensionen des konketen Falls abhängen. Für das wohldefinierte »Brechen« ist eine komplexe Abhängikeitsstruktur notewendig, die durch einen Rückstau komplett aus der Balance kippt. Das ist dann doch ein sehr spezieller Fall, denn in der Praxis erwarte ich im Durchschnitt ehr viele kleine 2-er und 3-er-Ketten ohne viel Verknüfpungen (ein IO-Job und ein Render-Job). Und bei länger laufenden Berechnungen wäre definitiv zu erwarten (ja sogar zwingend gefordert), daß sich freie Kapazität elastisch auf die nächsten Aufgaben verschieben läßt. +

+ + +
+
+ + + + +

+ Der Scheduler ist ein komplexer Mechanismus, und allein das Hochfahren des Worker-Pools, alsauch das Erstellen des initialen Schedule erzeugt einen festen Overhead, der mehr oder weniger unkontrolliert in den Start der Arbeitsphase hineinwirkt. Je nachdem wann und wo der erste Eintrag im Schedule auftaucht, erscheinen zu einem bestimmten Zeitpunkt alle Worker und sorgen erst mal für deutlich meßbare Contention-Verzögerungen. Des Weiteren erzeugt auch die Abhängigkeit auf den Wake-up-Job einen erheblichen Overhead, sobald es nicht mehr einen einzigen stringenten Berchnungspfad gibt. Hinzu kommt, daß wir nicht auf einem Realtime-Betriebssystem arbeiten, und die vermutete Basis-Last stark von der Optimierung abhängig ist. Und außerdem hat der Scheduler nun einen speziellen Schutzmechanismus, der den Worker-Pool beim Auftreten von Contention effektiv herunterregelt. All dies zusammen macht eine Beobachtung des »Leerlauf-Aufwandes« sehr schwer... +

+ + +
+
+ + + + +

+ Nebenbei sollten sich dabei auch nicht-lineare Basis-Effekte zeigen, aber idealerweise sollte das Verhalten ab einem gewissen Punkt linear werden; aus diesem näherungsweise linearen Verhalten ließe sich auf theoretischem Weg auch ein hypothetischer / amortisierter Basis-Aufwand ableiten. Die Hoffung ist, daß durch den Abgleich dieser verschiedenen Meßmethoden ein klareres Verständnis der Grenzen der Meßanordnung gewonnen werden kann. +

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

+ auto [stress,delta,time] = StressRig::with<Setup>() +

+

+                                      .perform<bench::BreakingPoint>(); +

+ +
+ +
@@ -112109,9 +112172,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

das beobachte ich mit dem DUMP-Logging immer wieder. Und zwar ohne eine ausgeprägte Contention-Spitze. Nach dem Absetzen des letzten regulären Node-Jobs sieht man das Log vom Continuation-Aufruf, dann lang nichts, dann die Sequenz der dependencies.