diff --git a/src/vault/mem/local-mem-pool.hpp b/src/vault/mem/local-mem-pool.hpp index 6a1a5d8bb..7f840e182 100644 --- a/src/vault/mem/local-mem-pool.hpp +++ b/src/vault/mem/local-mem-pool.hpp @@ -25,17 +25,25 @@ #define VAULT_MEM_LOCAL_MEM_POOL_H +#include "lib/error.hpp" //#include "vault/mem/buffer-provider-setup.hpp" //#include "vault/mem/engine-buffer-metadata.hpp" //#include "vault/mem/buffer-metadata.hpp" #include "vault/mem/buffhandle.hpp" //////////////TODO //#include "lib/local-slice.hpp" //#include "lib/depend.hpp" +#include "lib/nocopy.hpp" + +#include namespace vault { namespace mem { + namespace err = lumiera::error; + using std::tuple; + + class PoolDiagnostic; /** @@ -47,9 +55,71 @@ namespace mem { public: LocalMemPool() { } + + + /* ===== Pool Access API ===== */ + + bool empty() const { return 0 == size(); } + size_t size() const { return 0; } ///////////////////////OOO + + bool + canServe (size_t sizRequest) + { + return false; ///////////////////////////////////////OOO + } + + void + add (Buff* mem, size_t siz) + { + TODO ("add new alloc to pool"); + } + + void + reAdd (Buff* mem) + { + TODO ("return known alloc into pool"); + } + + tuple + retrieve (size_t sizRequest) + { + return {nullptr, 0}; + } + + private: + + friend class PoolDiagnostic; }; + class PoolDiagnostic + : util::MoveOnly + { + LocalMemPool memPool_; + public: + PoolDiagnostic (LocalMemPool& lmp) + : memPool_{lmp} + { } + + bool + isEmpty() + { + return true; + } ///////////////////OOO + + size_t + cnt (size_t siz) + { + return 0; ////////////////////OOO + } + }; + + + inline PoolDiagnostic + watch (LocalMemPool& lmp) + { + return PoolDiagnostic{lmp}; + } }} // namespace vault::mem #endif /*VAULT_MEM_LOCAL_MEM_POOL_H*/ diff --git a/tests/vault/mem/local-mem-pool-test.cpp b/tests/vault/mem/local-mem-pool-test.cpp index 1146791cd..f0e3edc71 100644 --- a/tests/vault/mem/local-mem-pool-test.cpp +++ b/tests/vault/mem/local-mem-pool-test.cpp @@ -38,13 +38,19 @@ namespace test { namespace { // Test helper -// template -// Buff* -// mark_as_Buffer(X& something) -// { -// return reinterpret_cast (std::addressof(something)); -// } -// + inline constexpr Buff* + fake_Buffer(size_t id) + { + return reinterpret_cast (id); + } + + Buff* const MEM1 = fake_Buffer(1); + Buff* const MEM2 = fake_Buffer(2); + Buff* const MEM3 = fake_Buffer(3); + + const size_t SIZ10{10}; + const size_t SIZ20{20}; + const size_t SIZ30{30}; } @@ -63,15 +69,52 @@ namespace test { { seedRand(); - verify_blah(); - verify_lolz(); + simpleUse(); + verify_matchAlloc(); + verify_selectAlloc(); + verify_pruneAlloc(); } - /** @test */ + /** @test show how to place an allocation into the pool */ void - verify_blah() + simpleUse() + { + LocalMemPool pool; + pool.add (MEM1, SIZ20); + CHECK (pool.canServe(SIZ20)); + CHECK (pool.canServe(SIZ10)); + + auto [mem,siz] = pool.retrieve (SIZ10); + CHECK (MEM1 == mem); + CHECK (SIZ20 == siz); + CHECK (not pool.canServe(SIZ10)); + + pool.reAdd (MEM1); + CHECK (pool.canServe(SIZ20)); + } + + + + /** @test + */ + void + verify_matchAlloc() + { + LocalMemPool pool; + CHECK (watch(pool).isEmpty()); + + pool.add (MEM1, SIZ10); + CHECK (watch(pool).cnt(SIZ10) == 1); + } + + + + /** @test + */ + void + verify_selectAlloc() { } @@ -80,7 +123,7 @@ namespace test { /** @test */ void - verify_lolz() + verify_pruneAlloc() { } }; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index ce9a39b40..8670c8f4f 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -82481,7 +82481,9 @@ - + + + @@ -82693,6 +82695,24 @@ + + + + + + + + + +

+ Automation, aber auch interaktive Controls können während einem laufenden Play-Vorgang eine lokale re-Konfiguration bewirken, ohne eine Neuplanung der Jobs zu erfordern; letzteres wird aber notwendig, wenn der Rendervorgang selber dynamische Elemente enthält, wie z.B. eine Loop, die während laufender Wiedergabe veränderbar ist. +

+ +
+ +
+
+
@@ -90291,7 +90311,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -116340,7 +116360,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + + @@ -124838,6 +124859,22 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
+ + + + + + + + +

+ ⟹ Konsequenz: es muß eine Synchronisations-Barriere in den Workern geben +

+ +
+
+ +
@@ -127480,6 +127517,17 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) + + + + + + + + + + + @@ -127496,7 +127544,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -144529,7 +144577,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -144683,16 +144731,13 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - - +

...was mich tatsächlich etwas verblüfft, aber es geht wohl grade glatt auf (und das bestätigt m.E. auch das API-Design)

- -
+
@@ -144705,22 +144750,17 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - - +

dort gibt es die Syntax watch(blockFlow), für den Zugang zu einem Diagnostic-Wrapper, der als friend deklariert ist, aber im Prod-Betrieb einfach nicht vorhanden ist. Durch ein solches Schema kann man sich bis in kritische Details hinein das Verhalten eines production-Grade Memory-Managers anschauen, ohne spezielle Mocks und Stubs einsetzen zu müssen.

- -
+
- - - +

Mit »zweite Ausbaustufe« meine ich, daß ein lokaler Pool da ist. Auch wenn dann später auch noch ein Austausch von Datenblöcken die direkte Allokation ergänzt. @@ -144732,23 +144772,19 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) Demnach muß ich von Anfang an eine solche Pool-Struktur vorsehen, obschon er zu Beginn niemals passiv Speicher vorhält

- -
+
- - - +

Diese Komponente entspricht also den BufferMetadata im LocalBufferStage: es ist ein technischer Implementierungs-Service, auf dessen Basis das BufferStore-API durch Abstraktion realisiert werden kann

- -
+
@@ -144758,20 +144794,57 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - - +

in Ticket #1366 (und auch oben in der Mindmap) habe ich definiert, wie ein gutes Framework aussehen sollte. Aber das ist mehr eine Wunschliste — was gebraucht würde wäre ein Satz kohärenter Concepts, denn es geht grade nicht darum, eine einzige »Library-Implementierung« zu schaffen; vielmehr geht es darum, die diversen Library-Container und Frameworks so unter einen Hut zu bekommen, daß sie composable werden.

- -
+
+ + + + + + + + + +

+ Ich sage sehr wahrscheinlich, weil ich die Möglichkeit im Blick habe, darunter mehrere Kachel-Pools zu betreiben. Kacheln machen nur Sinn, wenn sie isoliert zum Einsatz kommen können; sobald man Kacheln zusammenfügen muß, hat man effektiv ein anderes Speichermanagement-Schema, nämlich Fragmente — und die sind notorisch schwierig effizient zu handhaben. +

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

+ Wie schon mehrfach bemerkt, fast alle Informationen wären schon im metadata::Entry, so daß man gerne daran »anbauen« würde; aber der Lebenszyklus und die Aufteilung in zwei »Säulen« innerhalb des BufferProviders steht dem im Wege. Zudem verwalten wir in den Metadaten einen Buffer-Typ, und nicht nur einen Speicherblock (wenngleich auch das in den meisten Fällen auf das Gleiche hinauslaufen dürfte, da die TypeHandler vermutlich ehr eine selten benötigte Erweiterung sind) +

+ +
+ +
+
+
@@ -144851,27 +144924,21 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
- - - +

Stand: bis auf weiteres bereits fertig

- -
+ - - - +

Das erscheint bereits elaboriert genug, um damit eine echte Render-Engine zu betreiben. Wobei festzuhalten ist, daß das genze Konzept mit den hierarchiscen Metadaten bisher eine »frei drehende Idee« ist, und einer gründlichen Kritik unterzogen werden sollte.

- -
+
@@ -144911,6 +144978,969 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
+ + + + + + +

+ Bisher erfolgte die Betrachtung top-down, mit einem »BufferProvider« als Service, der die Bedürfnisse eines anderweitig bereits bestimmten Rendervorganges einfach befriedigt. Des Weiteren wurde in »Prozeß-Zustand« und »Allokation« unterschieden; erst an dieser Stelle, nachträglich, wurde die Koordination der Informationen über Thread-Grenzen hinweg bedacht. Die somit bereits vorgefaßten Kompartiments könnten die Erfassung des Sachverhalts, und damit die prospektive Lösung, unnötig komplex machen. +

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

+ dazu wird die Node-Chain traversiert, und für jeden Schritt der lokale Bedarf ermittelt; Wiederverwenden von Buffern passender Größe soll dabei ausdrücklich mit berücksichtigt werden (also nicht am Buffer-Typ festhalten, sondern allein den Speicherbedarf betrachten). Aber auch Caching-Points müssen beachtet werden (hier pessimistisch,  d.h. angenommen, es müßte eingespeichert werden) +

+ +
+
+ + + + +

+ wobei als »Typ-N« der größte oder naheliegendste Typ eingesetzt wird, sofern andere Typen den so bezeichneten Speicher wiederverwenden können +

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

+ Hauptsätzlich, um festzustellen ob dieser Play-Prozeß (derzeit) überhaupt starten kann, und ggfs. mit welchem Drosselfaktor (kann auch mit einem Rest-Puffer arbeiten, und einem gewissen Zuteilungs-Rhythmus, z.B. für Hintergrundaktivitäten; allerdings kann Speicher, anders als IO oder CPU, grundsätzlich nicht überprovisioniert werden) +

+ +
+
+ + + + + + +

+ ...denn ich gehe davon aus, daß diese Planung eines Play-Prozesses aus dem Session-Thread heraus erfolgt; insofern wäre es nicht zielführend, dafür auf ein System der thread-local Services zuzugreifen +

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

+ Da ein Play-Prozeß auch später, bei bereits laufender Engine gestartet oder geändert werden kann, ergibt sich das Problem daß die Worker-Threads die Änderungen am Model sehen müssen. Stren genommen müßte es dazu eine Read-Barrier irgendwo im Worker-Thread geben, denn sonst könnte, theoretisch, der Worker weiterhin einen alten Deskriptor sehen, den er noch im lokalen CPU-Cache hat. +

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

+ die zweite Alternative sollte man noch offen halten, und zwar wegen der Cache-Locality; es könnte nämlich durchaus sein, daß sich lokal der Speicherbedarf auf eine gewisse (ehr kleine) Anazahl an Entries einpendelt, die dann sinnvollerweise kompakt in einem Block lieden sollten, der dem aktuellen Thread gehört +

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

+ ...das meint, ich versuche zu vergessen, daß ich das größtenteils schon implementiert habe; ich weiß daß das unmöglich ist,  und daher weiß ich, daß diese Neu-Implementierung nicht »naiv« ist, sondern versucht, »anders zu sein«  — für den Zweck dieser Analyse ist das in Ordnung, denn ich möchte mich aus meinen festen Denkbahnen befreien +

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

+ wir müssen Typen eigentlich nicht wiederfinden +

+ +
+
+ + + + +

+ also z.B. daß ein Typ einen anderen Type-Handler hat, aber den gleichen Key +

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

+ das ist nämlich keineswegs garantiert, sondern hat lediglich eine sehr geringe Wahrscheinlichkeit, in der Größenordnung N/2^64 — genauer gesagt, es ist exakt die Wahrscheinlichkeit einer Hash-Kollision (welche wir typischerweise ignorieren) +

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

+ wie schon der globale Metadata-Store, so würde auch hier eine Verbindung als Neben-Effekt der statischen Natur von thread-local einfach zufallen: Alle Instanzen des gleichen LocalSlice (gleiche Template-Argumente) teilen sich eine Instanz per Thread +

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

+ das ist normalerweise ein Typ mit genau passender Storage; das heißt für jeden Pool mit fester Größe gibt es dann irgendwann auch einen entsprechenden BufferType +

+ +
+
+ + + + +

+ wenn der BufferStore bei der Allokation sieht, daß ein anderer Type verlangt wird (er sieht ja in mark_locked() explizit die verlangte Größe und er kann den parent-Key-Hash gegenchecken), dann kann er den Entry über seine Direkt-Verbindung auch bereits entfernen und erneut einhängen mit dem korrekten Parent-Hash. Die nachfolgende Metadaten-Verwaltung findet dann den bereits bestehenden Entry und arbeitet damit weiter +

+ +
+
+ + + + +

+ Wenn man entsprechend aufpaßt, könnte man das sogar locker handhaben — denn wenn man einen Entry in der Hand hat, hat man auch automatisch seinen Key und Hash mit in der Hand (weil er vom Key erbt). Letztlich muß der stets BufferStore entscheiden, welcher Entry konkret verwendet werden soll, und in dem Moment (beim nächsten mark_locked) kann er ihn auch wieder umhängen, egal wo er vorher hing. Wenn man dagegen Wert auf Konsistenz legt, dann kann der BufferStore im mark_released() den Entry wieder an seine generische Stelle umhängen, die zu dem Zeitpunkt garantiert existiert (sie wurde bereits früher synchronisiert) +

+ +
+
+
+ + + + + + +

+ ...das ist die direkteste Lösung, und vermeidet jedwede Gefahr von desynchronisiertem State. Die Ownership wird also nur darüber gesteuert, wer einen meta-Entry in der Hand hat +

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

+ das funktioniert ohne weiteren Aufwand, sofern man nur dafür sorgt, daß die globale Allokation technisch einen Owner hat, der sie beim Shutdown de-alloziert. Also z.B. indem man ihn in der globalen Hashtable irgendwo stehen läßt +

+ +
+
+ + + + +

+ versendet wird aber nur die Info (Buffer*, size) und zwar per Value +

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

+ ...unter der Annahme, daß wir Extent-basierend in Pools allozieren, genügt es, zu wissen, welcher »Slot« grade irgendwohin ausgeliehen wurde +

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

+ In dieser Variante würde die lokale Metadata-Registry nur die Typ-Einträge per Kopie allozieren, wenn sie synchronisiert. Da in dem Moment ohnehin ein Lock gehalten wird, könnte man sogar mit direkten Heap-Allokationen davonkommen. Denn die Masse der Meta-Entries gehört zu einzelnen Buffern, und diese würden ja stets im globalen Buffer-Manager alloziert, zusammen mit dem Buffer-Speicher +

+ +
+
+ + + + +

+ ...allerdings unter der Annahme, daß wir dann auch die BufferMetadata::Table mit einem lokal operierenden Extent-basierten Allokator hinterlegen +

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

+ unter der Annahme, daß asynchrones Messaging gegeben ist, und typischerweise sehr risikoarm implementiert werden kann, und ohne Gefahr von Deadlocks verwendbar ist. +

+ +
+
+
+
+ + + + + +

+ ...was aber erst sichtbar wurde, nachdem man alles ausformuliert hat; dann ist auch die »einfache« Variante gar nicht mehr so einfach +

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

+ Dadurch wird eine Verkopplung auf Implementierungs-Ebene vermieden, und der EngineBufferManager kann unabhängig implementiert werden +

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

+ ...dann bräuchte es gar kein »Backdoor« mehr +

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

+ Wenn wir eine Allokations-Anforderung stellen, ist es letztlich immer denkbar, daß diese nicht befriedigt werden kann. Das muß jetzt kein hartes »Out of Memory« sein, denn es liegt mutmaßlich ein custom-Allocator dazwischen, welcher jedoch auch interne Vorgaben haben kann, anhand derer gewisse Limits erkannt werden, bevor das System an die Wand fährt. +

+ +
+
+ + + + + + +

+ ...und zwar stand diese Entdeckung im Zusammenhand mit dem Output-Mechanismus, welcher sich durch aufgestaute Timeouts  sukzessive »zusetzen« kann.... +

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

+ ....sondern die Implementierung soll faktisch voll sichtbar sein, und damit komplett ge-Inlined werden. Warum? weil es letztlich um eine Implementierungs-Abstraktion handelt, und ich mir die Diskussion sparen möchte, ob ein Virtual Call performance-relevant sein könnte.  (Diese Dikusssion braucht mehr Aufwand, als das Ding einfach runterzucoden) +

+ +
+ +
+ + + + +

+ damit man das Feature gedanklich abtrennnen kann, und gesondert erklären +

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

+ diesen Typ müßte man vor der Template-Spezialisierung  bereits explizit festlegen +

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

+ ...und man kann das nur verbergen, indem man die Implementierung überhaupt ausläßt, und erst später, nach der Implementierung von BufferMetadata nachliefert. Das ist nicht nur verwirrend, sondern erfordert einen ungeprüften force-Cast in jeder Funktion +

+ +
+
+ + + + +

+ das heißt, zum Zeitpunkt des Aufrufes darf dieser Aufruf nicht mehr gegen das Interface gehen, sondern expressis verbis gegen einen Typ der volle Kenntnis von BufferMetadata hat, mit allen relevanten Implementierungsdetails +

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

+ Wenn das API so wie geplant funtkionieren soll, brauche ich eine Pointer-Manipulation, um vom Payload-Pointer zurück zum Entry-Pointer zu kommen. Im C++ - Standard gibt es das offsetof-Makro, das aber nur für Standard-Layout definiert ist. Aber hier haben wir ganz klar einen Mix aus privaten und protected-Feldern über die Vererbung hinweg. Theoretisch hätte damit der Compiler komplette Freiheit, wie er die Felder im Objekt anordnet (auch wenn das die praktisch bekannten Compiler bisher nicht so tun). Wenn ich diese Lösung trotzdem so »durchprügle« gäbe es jederzeit das Risiko, auch punktweise und nur in einzelnen Verwendungen, daß der Code entgleist und Daten korrumpiert. +

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

+ wie oben bereits skiziiert! +

+

+ Also eine komplett neue, einfachere Type-Registry, und dann die State-Transitions komplett in den BufferStore verlagern. Das versuche ich zu vermeiden, weil ich damit effektiv den gesamten BufferProvider nochmal komplett neu schreibe, inclusive der Verknüpfung mit OutputSlot. Das erscheint mir zu radikal, denn dann wäre ja alles Mist was ich bisher zu dem Thema gemacht habe. Klar, ich wäre beim zweiten Anlauf schneller, weil ich die Fallen ja bereits kenne. +

+ +
+ +
+ + + + + + +

+ Klartext: damit pervertiere ich das neue interne Design im BufferProvider +

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

+ das geht so weit, daß ich sie gestern, bei der Analyse irgendwie nicht gesehen habe, obwohl es doch mehr oder weniger direkt so herauskam +

+ +
+ + + + + +

+ Warum ist diese Variante so schrecklich, daß ich sie um jeden Preis vermeiden möchte? +

+ +
+ + + + + +

+ ich weiß nicht, warum ich dermaßen an denen klebe. ES GIBT KEINEN RATIONALEN GRUND DAFÜR. Also, so mein Verdracht, habe ich insgeheim deshalb diese »Lösung« aus dem Hut gezaubert, daß die Metadaten vom BufferStore mitverwendet werden. Denn dadurch wäre es gerechtfertigt, sie 1:N unter einen Typ zu hängen. Naja auch dann nicht wirklich, aber außer mir blickt da ja keiner durch. +

+

+ +

+

+ Mein Bauchgefühl sagt mit schon seit 2012, das ist es!! Chained Hashes. Eine Blockchain von Buffer-Typen. Genial. Irgendwie muß das die Struktur sein, und nun suche ich einen Grund dafür. +

+ +
+
+ + + + +

+ Wenn man mehr oder weniger den gleichen Verarbeitungsschritt zweimal in zwei verschiedenen Subsystemen machen muß, und dann sogar noch trickreich dafür sorgen muß, daß die nicht auseinanderlaufen — ist das dann nicht ein Hinweis darauf, daß die Subsysteme falsch geschnitten sind?? Zwar tritt diese Situation in der Praxis durchaus häufig auf, und oft nimmt man es einfach in Kauf, weil die Systeme zu groß und zu schwer sind, um sie noch zu ändern. Aber hier baue ich etwas komplett neu, sollte man da dann nicht kritischer sein?? +

+ +
+
+ + + + +

+ So wie der BufferProvider anfangs implementiert war, gäbe es alle diese Probleme nicht. Bloß war der alte Code so schwierig, daß er mir über den Kopf gewachsen war. Ich weiß zwar daß das andere Gründe hatte, nämlich daß ich die Tracking-Implementierung da reingewurschtelt hatte. Aber das ist ja nur passiert, weil das alles bereits zu komplex und unstrukturiert war. Ich konnte den Knoten erst lösen, nachdem ich diese Zweiteilung in State und Storage eingeführt habe. Und jetzt sollte sich ergeben, daß genau diese Zweiteilung gegen die Natur der Sache läuft??? +

+ +
+
+ + + + +

+ Und dabei eine Struktur schaffen, die keine Redundanz mehr hat: Also nur einen flachen Type-Store, und die State-Machine in den Allokations-Manager verlegen. Dann muß den aber auch die Proxy-Variante mitschleppen, also dann implementieren wir das zig mal. Das wäre also auch keine Lösung..... +

+ +
+
+ + + + +

+ Das wäre erst mal eine Type-Registry (thread-local, wie schon gelöst). Dann ein State-Manager. Und schließlich ein Storage-Manager. Der dann direkt mit dem State-Manager zusammenarbeitet. Das wäre dann aber wieder die »Hybrid-Lösung« +

+ +
+
+
+ + + + +

+ Es geht nämlich bei dieser ganzen Diskussion um eine LAPPALIE. Der Aufwand für diese Buffer-Verwaltung ist marginal, im Vergleich zur Allokation und erst recht zu den Medien-Berechnungen. +

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

+ die Eingangs-Queue muß lock-free sein +

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

+ zunächst wird nur diese genutzt +

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

+ Man kann nicht drei Minuten lang nicht an einen rosa Elefanten denken. Insofern bringt mich die Forderung, ein Über-Design zu verhindern, in einen permanenten Reflexions-Zirkel: ich muß mir ein »künstlich einfaches« Design ausdenken, und ich kann das nur denken, indem ich mutwillig Fehler mache. Wenn ich dann über die potentiellen Probleme nachdenke, kann ich die naturgemäß nicht einordnen, denn dafür bräuchte es Empirie. Diese Empirie kann aber in meinem Fall nur methodische Forschung sein. Denn die „natürliche Empirie“, nämlich einfach naiv anzufangen und aus den Problemen zu lernen, ist mir versperrt +

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

+ Feststellung: das Grund-Design der Engine mit dem Scheduler ist nie und nimmer »naiv« +

+ +
+ +
+ + + +
@@ -175468,6 +176498,9 @@ std::cout << tmpl.render({"what", "World"}) << s + + +