diff --git a/src/lib/allocation-cluster.hpp b/src/lib/allocation-cluster.hpp index 2b5a9c99c..a443e7485 100644 --- a/src/lib/allocation-cluster.hpp +++ b/src/lib/allocation-cluster.hpp @@ -127,6 +127,18 @@ namespace lib { template size_t count() const; + size_t + numExtents() const + { + UNIMPLEMENTED ("Allocation management"); + } + + size_t + numBytes() const + { + UNIMPLEMENTED ("Allocation management"); + } + private: /** diff --git a/tests/library/allocation-cluster-test.cpp b/tests/library/allocation-cluster-test.cpp index 7fdec0f04..657f7654c 100644 --- a/tests/library/allocation-cluster-test.cpp +++ b/tests/library/allocation-cluster-test.cpp @@ -28,24 +28,27 @@ #include "lib/test/run.hpp" #include "lib/test/test-helper.hpp" -#include "lib/util.hpp" -#include "lib/util-foreach.hpp" - #include "lib/allocation-cluster.hpp" -#include "lib/scoped-holder.hpp" +#include "lib/test/diagnostic-output.hpp"/////////////////TODO +#include "lib/iter-explorer.hpp" +#include "lib/util.hpp" +#include #include #include -#include +#include +//#include -using boost::lexical_cast; +//using boost::lexical_cast; +using lib::explore; using lib::test::showSizeof; -using util::for_each; using util::isnil; using ::Test; using std::numeric_limits; +using std::function; using std::vector; +using std::array; @@ -54,94 +57,74 @@ namespace test { namespace { // a family of test dummy classes - uint NUM_CLUSTERS = 5; - uint NUM_OBJECTS = 500; - uint NUM_FAMILIES = 5; + const uint NUM_CLUSTERS = 5; + const uint NUM_TYPES = 20; + const uint NUM_OBJECTS = 500; long checksum = 0; // validate proper pairing of ctor/dtor calls - bool randomFailures = false; template class Dummy { - char content[i]; + static_assert (0 < i); + array content_; public: - Dummy (char id=1) + Dummy (uchar id=1) { - content[0] = id; - checksum += id; - } - Dummy (char i1, char i2, char i3=0) - { - char id = i1 + i2 + i3; - content[0] = id; - checksum += id; - if (randomFailures && 0 == (rand() % 20)) - throw id; + content_.fill(id); + checksum += explore(content_).resultSum(); } - ~Dummy() + ~Dummy() { - checksum -= content[0]; + checksum -= explore(content_).resultSum(); } - char getID() { return content[0]; } + char getID() { return content_[0]; } }; - typedef ScopedHolder PCluster; - typedef vector ClusterList; - - inline char - truncChar (uint x) - { - return x % numeric_limits::max(); - } template void - place_object (AllocationCluster& clu, uint id) - { - clu.create> (id); - } + place_object (AllocationCluster& clu, uchar id) + { + clu.create> (id); + } - typedef void (Invoker)(AllocationCluster&, uint); - Invoker* invoke[20] = { &place_object<1> - , &place_object<2> - , &place_object<3> - , &place_object<5> - , &place_object<10> - , &place_object<13> - , &place_object<14> - , &place_object<15> - , &place_object<16> - , &place_object<17> - , &place_object<18> - , &place_object<19> - , &place_object<20> - , &place_object<25> - , &place_object<30> - , &place_object<35> - , &place_object<40> - , &place_object<50> - , &place_object<100> - , &place_object<200> - }; + inline array, NUM_TYPES> + buildTrampoline() + { + return { place_object<1> + , place_object<2> + , place_object<3> + , place_object<5> + , place_object<10> + , place_object<13> + , place_object<14> + , place_object<15> + , place_object<16> + , place_object<17> + , place_object<18> + , place_object<19> + , place_object<20> + , place_object<25> + , place_object<30> + , place_object<35> + , place_object<40> + , place_object<50> + , place_object<100> + , place_object<200> + }; + } void - fillIt (PCluster& clu) + fill (AllocationCluster& clu) { - clu.create(); - - if (20 (arg[0]); - if (1 < arg.size()) NUM_OBJECTS = lexical_cast (arg[1]); - if (2 < arg.size()) NUM_FAMILIES = lexical_cast (arg[2]); - simpleUsage(); - checkAllocation(); - checkErrorHandling(); + checkLifecycle(); } @@ -173,68 +151,37 @@ namespace test { { AllocationCluster clu; - char c1(123), c2(56), c3(3), c4(4), c5(5); - Dummy<44>& ref1 = clu.create> (); - Dummy<37>& ref2 = clu.create> (c1); - Dummy<37>& ref3 = clu.create> (c2); - Dummy<1234>& rX = clu.create> (c3,c4,c5); + char c1(123), c2(45); + Dummy<66>& ref1 = clu.create> (); + Dummy<77>& ref2 = clu.create> (c1); + Dummy<77>& ref3 = clu.create> (c2); - CHECK (&ref1); - CHECK (&ref2); - CHECK (&ref3); - CHECK (&rX); - TRACE (test, "%s", showSizeof(rX).c_str()); +// TRACE (test, "%s", showSizeof(rX).c_str());///////////////////////OOO + //returned references actually point at the objects we created + CHECK (1 ==ref1.getID()); CHECK (123==ref2.getID()); - CHECK (3+4+5==rX.getID()); - // shows that the returned references actually - // point at the objects we created. Just use them - // and let them go. When clu goes out of scope, - // all created object's dtors will be invoked. + CHECK (45 ==ref3.getID()); - CHECK (4 == clu.size()); - CHECK (1 == clu.count>()); - CHECK (2 == clu.count>()); - CHECK (1 == clu.count>()); + CHECK (1 == clu.numExtents()); + CHECK (66+77+77 == clu.numBytes()); + + // now use objects and just let them go; } void - checkAllocation() + checkLifecycle() { CHECK (0==checksum); { - ClusterList clusters (NUM_CLUSTERS); - for_each (clusters, fillIt); + vector clusters (NUM_CLUSTERS); + for (auto& clu : clusters) + fill(clu); CHECK (0!=checksum); } CHECK (0==checksum); } - - - void - checkErrorHandling() - { - CHECK (0==checksum); - { - randomFailures = true; - - AllocationCluster clu; - for (uint i=0; i> (i1,i2); - } - catch (char id) - { - checksum -= id; // exception thrown from within constructor, - } // thus dtor won't be called. Repair the checksum! - } - randomFailures = false; - CHECK (0==checksum); - } }; LAUNCHER (AllocationCluster_test, "unit common"); diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index c0152bb5c..35e1f8e56 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -81679,11 +81679,99 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ In diesem Konflikt stehen zwei gleichermaßen bedeutsame Belange gegeneinander, ohne einen klaren Ansatz zur Entscheidung +

+
    +
  • + ein erhebliches Wartungs-Risiko +
  • +
  • + ein erheblicher Performance-Overhead +
  • +
+

+ Der Beschluß zur Lösung sieht vor, diese Belange markierbar zu machen, um dann später differenziert handeln zu können. +

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

+ ...somit kann auf Basis der einzelnen, konkreten Datenstruktur entschieden (und später auch korrigiert) werden, ob ein expliziter clean-up-Aufruf notwendig ist; für die einzelne Datenstruktur dürfte das lokal jeweils klar entscheidbar sein, und ich erwarte, daß durch die Anbindung an den Allocation-Cluster diese Entscheidungsmöglichkeit auch langfristig klar dokumentiert ist — und zwar sollte das von üblichen C++ Praktiken abweichende Verhalten auch als der Spezialfall dargestellt sein (wenngleich auch erwartet wird, daß die meisten Datenstrukturen von diesem Spezialfall gebrauch machen) +

+ + +
+ + + + + + + + + + + + + + + + + + + + + + + @@ -81855,6 +81943,171 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + + + + + +

+ Kurzfristig erscheint das als eine naheliegende Optimierung, die einem praktisch »in den Schoß fällt« (die Implementierung wird dadurch sogar drastisch einfacher). Aber längerfristig befürchte ich eine heimtücksiche Gefahr, denn die hier genommene Abkürzung kann leicht übersehen werden, da sie den üblichen Gepflogenheiten zuwiderläuft. Im Lauf der Zeit können sich so Speicher- und Ressourcen-Lecks einschleichen, die dann nur mit erheblichem und fokussiertem Aufwand aufzuräumen sind +

+ + +
+
+ + + + + + +

+ Es handelt sich um eines der markanten Eigenschaften der Sprache C++ : Kontrolle und Determinismus bis ins kleinste Detail — und das prägt den alltäglichen Stil der Arbeit; weithin kann man sich auf Abstraktionen verlassen, weil diese sich wiederum auf Abstraktionen verlassen können; wenn alles genau und zuverlässig ist, dann werden auch weitreichende Aktionen planbar und handhabbar. +

+ + +
+
+ + + + + + +

+ Hier geht es um das gesamte low-level-Model, sowie möglicherweise Teile des Build-Prozesses und des Regelwerks, die daran angeknüpft sein könnten — und das bedeutet, mit einer (wie es zunächst scheint) sehr lokalen und tief verborgenen Optimierung könnte der Grund-Kontrakt in einem erheblichen Teil der Applikation geändert werden +

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

+ Der Aufwand, der allein für das Aufrufen der aller Destruktoren getrieben werden muß, ist nicht unerheblich, denn für jeden Typ muß eine Closure im Datensegment erzeugt werden und für jede einzelne Allokation muß diese per Funktionszeiger aufrufbar sein; außerdem muß die gesamte Allokation navigierbar gemacht werden — also zwei »Slots« zusätzlich für jede einzelne Allokation. Das ist sehr viel für eine Datenstruktur, die aus vielen kleinen und sehr flexiblen Descriptor-Elementen bestehen wird; die meisten Nodes haben erwartungsgemäß nur einen Eingang und einen Ausgang, was bedeutet, daß für jeweils nur eine einzige ID (ein »Slot«) zusätzlich ein Container (2 »Slot«) und dann noch 4 »Slot« Allokations-Overhead notwendig sind. +

+ + +
+
+ + + + + + +

+ in der Regel sind es cold pages +

+ + +
+ + + + + +

+ Aus Performance-Sicht besonders fatal ist, daß zum Zeitpunkt der Bulk-de-Allokation mit hoher Wahrscheinlichkeit alle betroffenen memory pages bereits »cold« sind, d.h. aus dem Cache herausgefallen; wir müssen also eine Menge von Speicherseiten über den Bus ziehen, bloß um sie zu navigieren und dann... +

+ + +
+
+ + + + + + +

+ ...in den allermeisten Fällen nämlich exakt gar nichts  zu tun. Dies unter der Annahme, daß die Struktur größtenteils selbst-referentiell ist; zwar werden dadurch reihenweise verkettete Destruktor-Aufrufe stattfinden, welche aber alle letztlich beim Allocator enden, welcher dann (ganz bewußt) nichts tut, weil der gesamte Speicherblock anschließend ohnehin verworfen wird. Da es sich jedoch um dynamisch aufgebaute Datenstrukturen handelt, kann der Optimizer diesen Leerlauf nicht erkennen und beseitigen +

+ + +
+
+ + + + + + +

+ Es steht zu befürchten, daß während der normalen Edit-Tätigkeit alle par 1/10-sec ein Builder-Lauf getriggert wird — und ich schätze, daß ein erheblicher Anteil der tatsächlichen Laufzeit in das Konstruieren der Datenstruktur geht, denn der zugrundeliegende trade-off ist ja grade  space-for-time. Wenngleich auch der Neubau ebenfalls schlecht für den Cache ist, so kann man doch zumindet in Teilen hoffen, daß die neu gebauten Strukturen zumindest bis zur ersten Berührung durch den Play-Prozeß im L3 bleiben. Für die alten Strukturen gilt das aber nicht, sie stellen rein nutzlosen Balast dar. +

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

+ das ist »der Klassiker«. +

+

+ Ich handle hier nur auf Basis eines Bauchgefühls, und alle Erfahrung zeigt, daß man dabei meist die Gewichte falsch setzt. +

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

+ Angenommen, ich mache diese Optimierung jetzt nicht, bereite sie aber vor; später dann zeigt sich (mit guter Wahrscheinlichkeit) tatsächlich ein relevanter Overhead ⟹ dann ist der Druck zur Optimierung umso stärker, und man wird die vorbereitete Option »ziehen« und die weitreichenden Konsequenzen in Kauf nehmen, da die Behebung eines konkreten Problems immer alle strategischen und methodischen Erwägungen übersteuert. Das wäre der schlechtest mögliche Verlauf, denn zu eine so späten Zeitpunkt kann man kaum mehr etwas tun, um eine weitreichende Änderung der Konventionen abzufedern +

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