From 154a7018beb13931a69ca2c02d125c596ff85459 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 7 Jun 2024 19:04:06 +0200 Subject: [PATCH] Library: attempt to build a re-alloc on top of the new adapter ...which basically ''seems doable'' now, yet turns up several unsolved problems - need a way to handle excess storage for the raw allocation - generally should relocate all metadata into the ArrayBucket - mismatch at various APIs; must re-think where to pass size explicitly - unclear yet how and where to pass the actual element type to create --- src/lib/several-builder.hpp | 122 ++++++++++++++++++------- src/lib/several.hpp | 31 ++++++- wiki/thinkPad.ichthyo.mm | 177 ++++++++++++++++++++++++++++++++++++ 3 files changed, 296 insertions(+), 34 deletions(-) diff --git a/src/lib/several-builder.hpp b/src/lib/several-builder.hpp index 3ee046fd0..65034f595 100644 --- a/src/lib/several-builder.hpp +++ b/src/lib/several-builder.hpp @@ -43,8 +43,11 @@ #define LIB_SEVERAL_BUILDER_H +#include "lib/error.hpp" #include "lib/several.hpp" +#include "include/limits.hpp" #include "lib/iter-explorer.hpp" +#include "lib/format-string.hpp" #include "lib/util.hpp" #include @@ -55,6 +58,8 @@ namespace lib { + namespace err = lumiera::error; + using std::vector; using std::forward; using std::move; @@ -62,7 +67,9 @@ namespace lib { namespace {// Allocation management policies - + using util::max; + using util::min; + using util::_Fmt; template class ALO> class ElementFactory @@ -105,13 +112,13 @@ namespace lib { using BucketAlloT = typename AlloT::template rebind_traits; auto bucketAllo = adaptAllocator(); - try { BucketAlloT::construct (bucketAllo, loc, spread); } + try { BucketAlloT::construct (bucketAllo, reinterpret_cast (loc), spread); } catch(...) { AlloT::deallocate (baseAllocator(), loc, storageBytes); throw; } - return static_cast (loc); + return reinterpret_cast (loc); }; template @@ -137,8 +144,8 @@ namespace lib { for (size_t i=0; isubscript(i)); - size_t storageBytes = calcSize (size, bucket->spread); - AlloT::deallocate (baseAllocator(), bucket, storageBytes); + size_t storageBytes = calcSize (size, bucket->spread); ////////////////////////////////////OOO das ist naiv ... es kann mehr Storage sein + AlloT::deallocate (baseAllocator(), reinterpret_cast (bucket), storageBytes); }; }; @@ -148,12 +155,56 @@ namespace lib { : ElementFactory { using Fac = ElementFactory; - using Fac::Fac; + using Bucket = ArrayBucket; - void* - realloc (void* data, size_t oldSiz, size_t newSiz) + using Fac::Fac; // pass-through ctor + + Bucket* + realloc (Bucket* data, size_t cnt, size_t& storage, size_t demand) { - UNIMPLEMENTED ("adjust memory allocation"); ///////////////////////////OOO Problem Objekte verschieben + if (demand == storage) + return data; + if (demand > storage) + {// grow into exponentially expanded new allocation + size_t spread = data? data->spread : sizeof(I); + size_t safetyLim = LUMIERA_MAX_ORDINAL_NUMBER * spread; + size_t expandAlloc = min (safetyLim + ,max (2*storage, demand)); + if (expandAlloc < demand) + throw err::State{_Fmt{"Storage expansion for Several-collection " + "exceeds safety limit of %d bytes"} % safetyLim + ,LERR_(SAFETY_LIMIT)}; + // allocate new storage block... + size_t newCnt = demand / spread; + if (newCnt * spread < demand) ++newCnt; + Bucket* newBucket = Fac::create (newCnt, spread); + // move (or copy) existing data... + ENSURE (data or cnt==0); + for (size_t i=0; i (newBucket, i + ,std::move_if_noexcept (data->subscript(i))); + ////////////////////////////////////////////////////////OOO schee... aba mia brauchn E, ned I !!!!! + // discard old storage + if (data) + Fac::template destroy (data, cnt); /////////////////////////////////////////////OOO Problem mit der überschüssigen Storage + storage = newCnt*spread; + return newBucket; + } + else + {// shrink into precisely fitting new allocation + Bucket* newBucket{nullptr}; + if (cnt > 0) + { + REQUIRE (data); + newBucket = Fac::create (cnt, data->spread); + for (size_t i=0; i (newBucket, i + ,std::move_if_noexcept (data->subscript(i))); ////////////OOO selbes Problem: E hier + Fac::template destroy (data, cnt); /////////////////////////////////////////////OOO nicht passende Storage!! + storage = cnt * data->spread; + } + return newBucket; + } } }; @@ -209,8 +260,8 @@ namespace lib { * Wrap a vector holding objects of a subtype and * provide array-like access using the interface type. */ - template - ,class E =I ///< a subclass element element type (relevant when not trivially movable and destructible) + template + ,class E =I ///< a subclass element element type (relevant when not trivially movable and destructible) ,class POL =HeapOwn ///< Allocator policy > class SeveralBuilder @@ -218,7 +269,7 @@ namespace lib { , util::MoveOnly , POL { - using Col = Several; + using Coll = Several; size_t storageSiz_{0}; @@ -236,7 +287,7 @@ namespace lib { SeveralBuilder&& reserve (size_t cntElm) { - adjustStorage (cntElm, sizeof(I)); + adjustStorage (cntElm, sizeof(E)); return move(*this); } @@ -258,19 +309,24 @@ namespace lib { void adjustStorage (size_t cnt, size_t spread) { - if (cnt*spread > storageSiz_) + size_t demand{cnt*spread}; + if (demand > storageSiz_) { // need more storage... - Col::data_ = static_cast (POL::realloc (Col::data_, storageSiz_, cnt*spread)); - storageSiz_ = cnt*spread; + Coll::data_ = static_cast (POL::realloc (Coll::data_, Coll::size_, storageSiz_, demand)); } - ENSURE (Col::data_); - if (spread != Col::data_->spread) + ENSURE (Coll::data_); + if (spread != Coll::data_->spread) adjustSpread (spread); - - if (cnt*spread < storageSiz_) + } + + void + fitStorage() + { + if (not Coll::data_) return; + size_t demand{Coll::size_ * Coll::data_->spread}; + if (demand < storageSiz_) { // attempt to shrink storage - Col::data_ = static_cast (POL::realloc (Col::data_, storageSiz_, cnt*spread)); - storageSiz_ = cnt*spread; + Coll::data_ = static_cast (POL::realloc (Coll::data_, Coll::size_, storageSiz_, demand)); } } @@ -278,20 +334,20 @@ namespace lib { void adjustSpread (size_t newSpread) { - REQUIRE (Col::size_); - REQUIRE (Col::data_); - REQUIRE (newSpread * Col::size_ <= storageSiz_); - size_t oldSpread = Col::data_->spread; + REQUIRE (Coll::size_); + REQUIRE (Coll::data_); + REQUIRE (newSpread * Coll::size_ <= storageSiz_); + size_t oldSpread = Coll::data_->spread; if (newSpread > oldSpread) // need to spread out - for (size_t i=Col::size_-1; 0spread = newSpread; + Coll::data_->spread = newSpread; } void @@ -300,8 +356,8 @@ namespace lib { REQUIRE (idx); REQUIRE (oldSpread); REQUIRE (newSpread); - REQUIRE (Col::data_); - byte* oldPos = Col::data_->storage; + REQUIRE (Coll::data_); + byte* oldPos = Coll::data_->storage; byte* newPos = oldPos; oldPos += idx * oldSpread; newPos += idx * newSpread; @@ -314,14 +370,14 @@ namespace lib { { using Val = typename IT::value_type; size_t elmSiz = sizeof(Val); - adjustStorage (Col::size_+1, requiredSpread(elmSiz)); + adjustStorage (Coll::size_+1, requiredSpread(elmSiz)); UNIMPLEMENTED ("emplace data"); } size_t requiredSpread (size_t elmSiz) { - size_t currSpread = Col::empty()? 0 : Col::data_->spread; + size_t currSpread = Coll::empty()? 0 : Coll::data_->spread; return util::max (currSpread, elmSiz); } }; diff --git a/src/lib/several.hpp b/src/lib/several.hpp index cbe84be5b..ab0bb36b8 100644 --- a/src/lib/several.hpp +++ b/src/lib/several.hpp @@ -23,7 +23,36 @@ /** @file several.hpp - ** Abstraction interface: array-like access by subscript + ** Abstraction interface: array-like random access by subscript. + ** + ** # Design + ** + ** This is a data structure abstraction suitable for performance critical code. + ** It is used pervasively in the backbone of the Lumiera »Render Node Network«. + ** - usage is clear and concise, allowing to hide implementation details + ** - adaption and optimisation for various usage patterns is possible + ** - suitably fast read access with a limited amount of indirections + ** \par why not `std::vector`? + ** The most prevalent STL container _almost_ fulfils the above mentioned criteria, + ** and thus served as a blueprint for design and implementations. Some drawbacks + ** however prevent its direct use for this purpose. Notably, `std::vector` leaks + ** implementation details of the contained data and generally exposes way too much + ** operations; it is not possible to abstract away the concrete element type. + ** Moreover, using `vector` with a custom allocator is surprisingly complicated, + ** requires to embody the concrete allocator type into the container type and + ** requires to store an additional back-link whenever the allocator is not + ** a _monostate._ The intended use case calls for a large number of small + ** collection elements, which are repeatedly bulk- allocated and deallocated. + ** + ** The lib::Several container is a smart front-end and exposes array-style + ** random access through references to a interface type. It can only be created + ** and populated through a builder, and is immutable during lifetime, while it + ** can hold non-const element data. The actual implementation data types and the + ** allocator framework used are _not exposed in the front-end's type signature._ + ** The container is single-ownership (move-asignable); some additional metadata + ** and the data storage reside in an `ArrayBucket`, managed by the allocator. + ** In its simplest form, this storage is heap allocated and automatically deleted. + ** ** @todo as of 2016, this concept seems very questionable: do we _really_ want ** to abstract over random access, or do we _actually_ want for-iteration?? ** @warning WIP-WIP-WIP in rework 5/2025 diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 93061696c..f06efaaf1 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -82293,6 +82293,37 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -82326,6 +82357,39 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -82336,6 +82400,119 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + + + + + + + +

+ das ist jetzt noch ein weiteres extra Datenfeld +

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

+ denn der Allocation-Cluster weiß selber die Zahl seiner belegten Roh-Blöcke; zur de-Allokation muß ansonsten gar nichts gemacht werden +

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

+ Der vermutlich häufigste Fall ist ein Several<ProcNode*> — mit typischerweise genau einem Element mit einem »Slot«Größe. Dafür haben wir jetzt schon 4 »Slot« Overhead, und jetzt sollen das 5 »Slot« werden.... +

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

+ ...das heißt, wir verwenden im ersten »Slot« ein Bit-Feld, da die Zahl der Elemente ohnehin praktisch begrenzt ist (auf ein paar Hundert); in den freien oberen Bits kann daher das konkrete weitere Layout encodiert werden. Das kostet nur einen minimalen Runtime-Overhead (ein paar Bit-Manipulationen vor der Indirektion zum Datenelement, welches ebenfalls im Cache liegt). Der intendierte use-case nutzt diese Collection als Basis einer verzeigerten Datenstruktur, und nicht in einer innersten Loop. +

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

+ besonderes geschickt: man kann das „später mal“ machen +

+ + +
+ + + + + +

+ Denn Lumiera hat im Moment wirklich andere Sorgen, als die Optimierung einer nocht-gar-nicht-wirklich-genutzten Datenstruktur +

+ + +
+
+
+
+
+