Reference-Counted Array variabler Länge mit minimalem Overhead (shared_ptr, o.ä.)
-
Hallo zusammen!
ich suche nach weiteren Lösungsansätzen für ein Problem bei dem ich vermeiden möchte, die Lösung "zu Fuß" zu implementieren und bei dem mir gerade die Ideen ausgehen.
Vielleicht fällt euch ja etwas ein?
Problem:
Ich möchte ein Array oder eine vergleichbare Datenstruktur variabler Länge möglichst effizient mittels Reference Counting verwalten.
Die Länge der Arrays ist erst zur Laufzeit bekannt, und soll auch veränderbar sein. Im Prinzip suche ich so etwas:T* a = new T[N]; ... delete [] a; a = new T[M];... nur dass das Array eben über einen
shared_ptroder etwas Vergleichbarem verwaltet wird.
Ferner gibt es noch folgende Anforderung, welche viele naheliegende Lösungen nicht praktikabel macht:Minimaler Overhead: Mit Ausnahme der atomaren Operationen und den zusätzlichen Speicher für das Reference Counting soll die Datenstruktur die selben Ausführungs- und Speicherzugriffscharakteristiken haben wie o.g.
new/delete-Konstrukt haben.
Insbesondere: Keine zusätzlichen Allokationen und Cache-freudlicher Zugriff auf die Elemente (d.h. keine load-Operationen auf Speicherbereiche in deren Nachbarschaft keine Array-Elemente liegen).Bisherige Ansätze:
1.
std::shared_ptr<std::vector<T>>Leider ungeeignet, da der Zugriff auf die Elemente eine zusätzliche Dereferenzierung erfordert.
std::shared_ptr und std::vector sind meines wissens so oder ähnlich implementiert:class shared_ptr { ... T* object; ... } class vector { ... T* elements; ... }d.h. um auf ein Element von
vectorzuzugreifen muss zunächstobjectund dannelementsdereferenziert werden, es wird also oft eine Cache-Line geladen um lediglich an denelements-Pointer zu gelangen (und eventuell die Länge des Vektors), ohne dass ich beim Zugriff auf die eigentlichen Elemente davon weiter profitieren könnte. Zugegeben, beim Durchlaufen besonders langer Arrays wird sich das zusätzliche Dereferenzieren wahrscheinlich amortisieren, ich suche aber nach einer Lösung die diesen Overhead dennoch vermeidet.2.
std::shared_ptr<std::array<T, N>>Ungeeignet, da die Länge des Arrays während des Kompilierens bekannt sein muss und diese Teil des Typs ist (soll zugewiesen werden können, auch wenn linke und rechte Seite der Zuweisung unterschiedliche Array-Längen haben)
3.
std::shared_ptr<T>mit Custom-Deleter:std::shared_ptr<T> a(new T[N], std::default_delete<T[]>());... sieht nach einer guten Lösung aus, benötigt aber 2 Allokationen (
new T[N]und Kontrollblock vonshared_ptr, Exception-safety erstmal außen vor) und der Kontrollblock ist im Speicher nicht mit den Elementen den benachbart.
std::make_sharedwäre die ideale Lösung, unterstützt allerdings leider nicht die Konstruktion von Arrays in der o.g. Form
Falls jemand mit so etwas schonmal Erfahrungen gesammelt hat, und einen guten Lösungsansatz hat, wäre ich dankbar. Das per Hand zu implementieren ist ein ziemlicher Schlepp den ich gerne vermeiden würde

Gruss,
Finnegan
-
Evtl. könntest du mit allocate_shared tricksen.
-
-
@Intr Usoff
Intrusive-Pointer sollten eine gute Möglichkeit sein, wenn die Array-Elemente immer PODs sind.
Mit nicht-POD Typen wüsste ich auf die Schnelle aber nicht wie man das anstellen soll. Ausser halt wieder viel Code selbst zu schreiben.
-
Finnegan schrieb:
Zugegeben, beim Durchlaufen besonders langer Arrays wird sich das zusätzliche Dereferenzieren wahrscheinlich amortisieren, ich suche aber nach einer Lösung die diesen Overhead dennoch vermeidet.
Wäre SmallVector eine Option? Also immer Speicher für ca. 16 Elemente bereithalten und wenn das überschritten wird, dynamisch Speicher anfordern. Nicht maximal effizient, aber dafür einfach.
-
Nathan schrieb:
Evtl. könntest du mit allocate_shared tricksen.
Intr Usoff schrieb:
Was ist mit http://www.boost.org/doc/libs/1_57_0/libs/smart_ptr/intrusive_ptr.html ?
Hey! Boost und
allocate_sharedwaren die richtigen Stichworte, die mich auf die richtige Fährte gebracht haben (aus Boost 1.57-Doku):template<class U> shared_ptr<U> make_shared(size_t size); template<class U, class A> shared_ptr<U> allocate_shared(const A& allocator, size_t size); Returns: A shared_ptr to a value-initialized object of type T[size]. Remarks: These overloads shall only participate in overload resolution when U is of the form T[]. Examples: boost::shared_ptr<int[]> a1 = boost::make_shared<int[]>(size); boost::shared_ptr<int[][2]> a2 = boost::make_shared<int[][2]>(size);Scheint so, als könne der
boost::shared_ptrgenau das was ich beimstd::shared_ptrvermisst habe! Jetzt, wo so vieles aus Boost Einzug in die Standard Library gehalten hat, bin ich gar nicht mehr drauf gekommen, dass Boost auch einenshared_ptrhat. Ich werd's mal damit probieren
Vielen Dank jedenfalls!Finnegan
P.S.: Jetzt ist es mir schon ein bisschen peinlich, dass ich das nicht vorher gefunden habe - diese Lösung habe ich sogar in den Suchmaschinenergebnissen übersehen
(Schande über mich, LMGTFY lasst grüssen). Sorry!
BTW: Scheint sogar mit N3641 ein Standard-Proposal zu geben, in dem vorgeschlagen wird, das auch instdaufzunehmen. Könnte also sein, das sowas demnächst von Haus aus unterstützt wird.