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
@@ -7452,17 +7452,15 @@ The example presented to the right uses a similar setup (''8 workers''), but red
As net effect, most of the load peaks are just handled by two workers, especially for larger load sizes; most of the available processing capacity remains unused for such short running payloads. Moreover, on average a significant amount of time is spent with partially blocked or impeded operation (→ light green circles), since administrative work must be done non-concurrently. Depending on the perspective, this can be seen as a weakness -- or as the result of a deliberate trade-off made by the choice of active work-pulling and a passive Scheduler.
-The actual average in-job time (→ dark green dots) is offset significantly here, and closer to 400µs -- which is also confirmed by the gradient of the linear model (0.4ms / 2 Threads ≙ 0.2ms/job). With shorter load sizes below 90 jobs, increased variance can be observerd, and measurements can no longer be subsumed under a single linear relation -- in fact, data points seem to be arranged into several groups with differing, yet mostly linear correlation, which also explains the negative socket value of the overall computed model; using only the data points with > 90 jobs would yield a model with slightly lower gradient but a positive offset of ~2ms.
+The actual average in-job time (→ dark green dots) is offset significantly here, and closer to 400µs -- which is also confirmed by the gradient of the linear model (0.4ms / 2 Threads ≙ 0.2ms/job). With shorter load sizes below 90 jobs, increased variance can be observed, and measurements can no longer be subsumed under a single linear relation -- in fact, data points seem to be arranged into several groups with differing, yet mostly linear correlation, which also explains the negative socket value of the overall computed model; using only the data points with > 90 jobs would yield a model with slightly lower gradient but a positive offset of ~2ms.
<html><div style="clear: both"/></html>
Further measurement runs with other parameter values fit well in between the two extremes presented above. It can be concluded that this Scheduler implementation strongly favours larger job sizes starting with several milliseconds, when it comes to processing through a extended homogenous work load without much job interdependencies. Such larger lot sizes can be handled efficiently and close to expected limits, while very small jobs massively degrade the available performance. This can be attributed both to the choice of a randomised capacity distribution, and of pull processing without a central manager.
!!!Stationary Processing
+The ultimate goal of //load- and stress testing// is to establish a notion of //full load// and to demonstrate adequate performance under //nominal load conditions.// Thus, after investigating overheads and the breaking point of a complex schedule, a measurement setup was established with load patterns deemed „realistic“ -- based on knowledge regarding some typical media processing demands encountered for video editing. Such a setup entails small dependency trees of jobs loaded with computation times around 5ms, interleaving several challenges up to the available level of concurrency. To determine viable parameter bounds, the //breaking-point// measurement method can be applied to an extended graph with this structure, to find out at which level the computations will use the system's abilities to such a degree that it is not able to move along faster any more.
+<html><img title="Load topology for stationary processing with 8 cores" src="dump/2024-04-08.Scheduler-LoadTest/Topo-20.svg" style="width:100%"/></html>
-lorem ipsum
-lorem ipsum nebbich
-ja luia sog I
-
-
+This research revealed again the tendency of the given Scheduler implementation to ''scale-down capacity unless overloaded''. Using the breaking-point method with such a fine grained and rather homogenous schedule can be problematic, since a search for the limit will inevitably involve running several probes //below the limit// -- which can cause the scheduler to reduce the number of workers used to a level that fills the available time. Depending on the path taken, the search can thus find a breaking point corresponding to a throttled capacity, while taking a search path through parameter ranges of overload will reveal the ability to follow a much tighter schedule. While this is an inherent problem of this measurement approach, it can be mitigated to some degree by limiting the empiric adaption of the parameter scale to the initial phase of the measurement, while ensuring this initial phase is started from overload territory.
...ich hatte diesen Fix nur oberflächlich getestet, und dabei übersehen, daß eine Assertion ansprechen kann (sogar sehr wahrscheinlich einmal ansprechen wird, sobald der Reparatur-Mechanismus eine größtere Strecke zurücklegt). Das ist aber kein Bug im eigentlichen Reparatur-/reLink-Mechanismus; dieser funktioniert präzise, wie ich nochmals im einzelnen mit dem Debugger nachvollziehen konnte.
- - +
wenn eine Deadline überfahren wurde, ist ein weiterer Zugriff auf den Extend als _undefined behaviour_ zu betrachten. Das gilt auch für das AllocatorHandle, das man früher mal für eine bestimmte Deadline bekommen hat; dieses kann man sehr wohl weiterhin verwenden (solange die Deadline noch in der Zukunft liegt). Konkreter Fall: später noch eine Dependency anhängen. Wenn der Anker dieser Dependecy zu dem Zeitpunkt bereits ausgeführt oder invalidiert wurde, ist man selber schuld!
@@ -116289,9 +116284,7 @@ std::cout << tmpl.render({"what", "World"}) << s
und damit nicht über eine Abweichung der Job-Zeiten in den Formfaktor eingehen
@@ -116302,9 +116295,7 @@ std::cout << tmpl.render({"what", "World"}) << s
naja... die ist unterirdisch
@@ -116314,9 +116305,7 @@ std::cout << tmpl.render({"what", "World"}) << s
zur Erinnerung: in einer Serie machen wir ja eine Art Konvergenz auf einen effektiven Form-Faktor hin. Mit den Ergebnissen eines Laufes wird für den nächsten Lauf nachjustiert; der von außen vorgegebene (nominelle) Streß-Faktor bleibt, aber die tatsächliche Dichte wird so optimiert, daß die dann effektiv diesem Faktor entspricht. Im Zuge dieser Anpassung wird anscheinend das Schedule jeweils etwas verdichtet, und die erreichte Concurency fällt (von etwas über 2 auf 1.6 zuletzt)
@@ -116327,9 +116316,7 @@ std::cout << tmpl.render({"what", "World"}) << s
zumindest nach den inzwischen vorliegenden Beobachtungen aus dem Param-Range-Setup
@@ -116347,10 +116334,138 @@ std::cout << tmpl.render({"what", "World"}) << s
+ damit geht jetzt die Auslastung einigermaßen hoch
+
+ es ist und bleibt ein Kompromiß....
+
+ Ich versuche hier, eine sehr spezifische Meßmethode halbwegs generisch nutzbar zu machen, stecke dabei aber bereits gefährlich viel Vorannahmen über den Scheduler in den Meßprozeß
+
+ Kapazität wird normalerweise zufällig in eine aktive Zone verteilt; nur wenn wir hinter das Schedule fallen, werden alle Worker eingesetzt
+
+ ...wenn aufgrund einer vorhergehend beobachteten, geringen Parallelität das Schedule gespreizt ist, dann ist die Abarbeitung eines Layers vorzeitig fertig, und die Worker werden hinter den Startpunkt des nächsten Levels verteilt. Damit geht auch dort wieder die Kapazität nur langsam hoch, und nach wenigen Runden hat sich eine kleine Zahl an aktiven Workern herauskristalisiert. Die weitere Nachregulierung sorgt dann genau dafür, daß das Schedule so großzügig ist, daß diese wenigen Worker es schaffen.
+
+ dieses Setup beobachtet nicht den »breaking Point«
+
+ — sondern das Erreichen eines Lastziels
+
+ Ursprünglich wurde die »breaking Point«-Methode an einem komplexen Lastmuster entwickelt, welches bei Überlastung sehr deutlich degeneriert, insofern dann zentrale Vorraussetzungs-Knoten erst spät erreicht werden, und damit das gesamte Schedule sich drastisch verspätet. Sowas ist hier nicht gegeben. Vielmehr verlängert sich die Laufzeit einfach elastisch und proportional, wenn ein einmal vorgegebenes Schedule ohne Puffer genau erfüllt wird. Da sich die Suche von der Seite geringer Last nähert, wird dabei nie genügend Druck aufgebaut, um die Concurrency hochzutreiben.
+
+ locker ⟹ geringe Concurrency ⟹ dieser Punkt wird als strenger klassifiziert und das Schedule wird noch lockerer ⟹ wenn nun wir an den vorherigen Testpunkt zurückkehren würden, dann wäre dort möglicherweise das Testziel (= Schedule gebrochen) gar nicht mehr erfüllt
+
...damals allerdings aus einem ganz anderen Kontext, der inzwischen durch einen Umbau im Scheduler behoben ist/sein sollte.
@@ -116377,9 +116492,7 @@ std::cout << tmpl.render({"what", "World"}) << s
»investigateWorkProcessing«
@@ -116405,9 +116518,7 @@ std::cout << tmpl.render({"what", "World"}) << s
0000000609: PRECONDITION: extent-family.hpp:356: thread_9: access: (isValidPos (idx))
@@ -116497,9 +116608,7 @@ std::cout << tmpl.render({"what", "World"}) << s
linkToPredecessor()
@@ -116520,9 +116629,7 @@ std::cout << tmpl.render({"what", "World"}) << s
tritt reproduzierbar auf ab Load=4ms
@@ -116554,9 +116661,7 @@ std::cout << tmpl.render({"what", "World"}) << s
alloziert wurde auf dem predecessor-Term
@@ -116599,16 +116704,13 @@ std::cout << tmpl.render({"what", "World"}) << s
...das ist ohnehin etwas kreativ
dieser hängt an der Deadline
Der Allokator in sich ist robust; die Deadline beschreibt nur einen Nutzungs-Kontrakt; sie ist zwar im Gate des Blocks gespeichert, aber für den Allokator nur maßgeblich zur Suche des passenden Blocks. Das weitere Nutzungs-Muster muß auf einer höheren Ebene gewährleistet sein.
Und zwar unabhängig davon, ob die Kalibrierung mit kurzen oder langen Zeiten und single- oder multithreaded erfolgte. Die Abweichtung tritt nur im realen Last-Kontext auf, und ist (visuell. den Diagrammen nach zu urteilen) korreliert mit dem Grad an contention und irregularität im Ablauf. Tendentiell nimmt sie für längere Testläufe ab, konvergiert aber — auch für ganz große Lasten und sehr lange Läufe — typischerweise zu einem Offset von ~ +1ms
@@ -116687,9 +116781,7 @@ std::cout << tmpl.render({"what", "World"}) << s
Und das ist schon aus rein-logischen Gründen so zu erwarten. Bewußt habe ich beim Aufstellen der Heuristig für das Test-Schedule auf jedwede optimale Anordnung der Rechenwege verzichtet (kein Box-stacking problem lösen!). Hinzu kommen die tatsächlichen Beschränkungen des Worker-Pools. Daraus ergibt sich eine charakteristische Abweichung zwischen einem theoretisch berechneten concurrency-speed-up (wie er in's Schedule eingerechnet ist) und der empirisch beobachteten durchschnittlichen concurrency. Das wird als Form-Faktor gedeutet.
@@ -116703,9 +116795,7 @@ std::cout << tmpl.render({"what", "World"}) << s
zwischen Load-Size und Laufzeit zum kompletten Abarbeiten der erzeugten Lastspitze
@@ -116717,9 +116807,7 @@ std::cout << tmpl.render({"what", "World"}) << s
Gradient sehr nah am zu erwartenden Wert
@@ -116727,9 +116815,7 @@ std::cout << tmpl.render({"what", "World"}) << s
wenn man die empirisch beobachtete, effektive Concurrency und reale durchschnittliche Job-Zeit ansetzt
@@ -116739,9 +116825,7 @@ std::cout << tmpl.render({"what", "World"}) << s
das bedeutet: der tatsächlich beobachtete Sockel hängt von der Länge der Job-Last und der Concurrency ab: Grundsätzlich muß man einmal die ganze Worker-Pool-size zu Beginn und am Ende aufschlagen — mit reduzierter Concurrency. Das ergibt sich bereits aus einer rein logischen Überlegung: »Voll-Last« kann erst konstituiert werden, wenn der erste Worker sich den zweiten Job holt. Analog beginnt der spin-down, wenn der erste worker idle fällt.