Frage

Was sind einige wirklich gute Gründe std::allocator für eine kundenspezifische Lösung zu Graben? Haben Sie in allen Situationen führen, wo es absolut für Richtigkeit notwendig war, Leistung, Skalierbarkeit, etc? Irgendwelche wirklich clevere Beispiele?

Benutzerdefinierte Verteilern haben immer ein Merkmal der Standardbibliothek, die ich nicht viel Notwendigkeit gehabt haben. Ich habe mich nur gefragt, ob jemand hier auf SO einige überzeugende Beispiele geben könnte ihre Existenz zu rechtfertigen.

War es hilfreich?

Lösung

Wie ich hier erwähnen, habe ich Intel TBB des benutzerdefinierten STL gesehen allocator erheblich Leistung eines Multi-Thread-App verbessern, indem einfach eine einzelne

std::vector<T>

std::vector<T,tbb::scalable_allocator<T> >

(dies ist eine schnelle und bequeme Möglichkeit, das Allocator des Umschaltens TBB der geschickten fade privaten Haufen zu verwenden, siehe Seite 7 in diesem Dokument )

Andere Tipps

Ein Bereich, wo benutzerdefinierte Verteilern nützlich sein kann, ist die Entwicklung von Spielen, vor allem auf Spielkonsolen, da sie nur eine geringe Menge an Speicher und ohne Swap haben. Auf solchen Systemen wollen Sie sicherstellen, dass Sie eine strenge Kontrolle über jedes Teilsystem aufweisen, so dass ein unkritisches System nicht auf den Speicher von einem kritischen einem stehlen kann. Andere Dinge wie Pool Verteilern können helfen, Speicherfragmentierung zu reduzieren. Sie können ein lange, ausführliches Papier zum Thema finden Sie unter:

EASTL - Electronic Arts Standard Template Library

Ich arbeite an einem mmap-Allocator die Vektoren Speicher verwenden können aus eine Memory-Mapped-Datei. Das Ziel ist, Vektoren zu haben, die Speicher verwenden, ist direkt in den virtuellen durch mmap abgebildet Speicher. Unser Problem ist, verbessern Lesen von wirklich großen Dateien (> 10 GB) in den Speicher ohne Kopie deshalb Overhead, ich brauche dieses benutzerdefinierte allocator.

Bisher habe ich das Skelett eines benutzerdefinierten allocator (Das ergibt sich aus std :: Allocator), ich denke, es ist ein guter Anfang ist verweisen auf eigene Verteilern zu schreiben. Fühlen Sie sich frei, dieses Stück Code zu verwenden, in welcher Weise auch immer Sie wollen:

#include <memory>
#include <stdio.h>

namespace mmap_allocator_namespace
{
        // See StackOverflow replies to this answer for important commentary about inheriting from std::allocator before replicating this code.
        template <typename T>
        class mmap_allocator: public std::allocator<T>
        {
public:
                typedef size_t size_type;
                typedef T* pointer;
                typedef const T* const_pointer;

                template<typename _Tp1>
                struct rebind
                {
                        typedef mmap_allocator<_Tp1> other;
                };

                pointer allocate(size_type n, const void *hint=0)
                {
                        fprintf(stderr, "Alloc %d bytes.\n", n*sizeof(T));
                        return std::allocator<T>::allocate(n, hint);
                }

                void deallocate(pointer p, size_type n)
                {
                        fprintf(stderr, "Dealloc %d bytes (%p).\n", n*sizeof(T), p);
                        return std::allocator<T>::deallocate(p, n);
                }

                mmap_allocator() throw(): std::allocator<T>() { fprintf(stderr, "Hello allocator!\n"); }
                mmap_allocator(const mmap_allocator &a) throw(): std::allocator<T>(a) { }
                template <class U>                    
                mmap_allocator(const mmap_allocator<U> &a) throw(): std::allocator<T>(a) { }
                ~mmap_allocator() throw() { }
        };
}

Um dies zu nutzen, einen STL-Container wie folgt deklarieren:

using namespace std;
using namespace mmap_allocator_namespace;

vector<int, mmap_allocator<int> > int_vec(1024, 0, mmap_allocator<int>());

Es kann beispielsweise verwendet werden, um sich einzuloggen, wenn der Speicher zugeordnet ist. Was ist notwendig, struct ist die rebind, sonst der Vektor Container verwendet das Super Zuweisen / Frei Methoden.

Update: Die Allocator Speicherzuordnung ist jetzt verfügbar unter https://github.com/johannesthoma/mmap_allocator und ist LGPL. Fühlen Sie sich frei, um es für Ihre Projekte zu verwenden.

Ich arbeite mit einer MySQL-Speicher-Engine, die c ++ für seinen Code verwendet. Wir verwenden eine benutzerdefinierte allocator das MySQL-Speichersystem zu verwenden, anstatt mit MySQL im Wettbewerb für das Gedächtnis. Es erlaubt uns, um sicherzustellen, dass wir Speicher verwenden, wenn der Benutzer MySQL konfiguriert zu verwenden, und nicht „extra“.

anstelle des Haufens

Es kann nützlich sein, benutzerdefinierte Verteilern zu verwenden, um einen Speicherpool zu verwenden. Das ist ein Beispiel unter vielen anderen.

In den meisten Fällen ist dies sicherlich eine vorzeitige Optimierung. Aber es kann in bestimmten Kontexten sehr nützlich sein (Embedded-Geräte, Spiele, etc.).

Ich habe nicht C ++ Code mit einem benutzerdefinierten STL Allocator geschrieben, aber ich kann einen Webserver in C ++, geschrieben vorstellen, die ein benutzerdefiniertes Allocator für die automatische Löschen von temporären Daten verwendet, die für auf eine HTTP-Anforderung zu reagieren. Der Brauch allocator können alle temporären Daten frei auf einmal, sobald die Reaktion erzeugt wurde.

Ein weiterer möglicher Anwendungsfall für eine benutzerdefinierte Allocator (die ich verwendet habe) schreibt einen Unit-Test zu beweisen, dass das Verhalten eine Funktion nicht auf einem Teil seiner Eingabe abhängt. Der Brauch allocator den Speicherbereich mit einem beliebigen Muster füllen kann.

Wenn Sie mit GPUs oder anderen Co-Prozessoren arbeiten es manchmal von Vorteil ist, Datenstrukturen im Hauptspeicher in einem besondere Art und Weise zuzuweisen. Diese besondere Art und Weise der Zuweisung von Speicher können in einem benutzerdefinierten allocator in einer bequemen Art und Weise umgesetzt werden.

Der Grund, warum individuelle Zuordnung durch den Beschleuniger Laufzeit kann von Vorteil sein, wenn Beschleunigern ist die folgende:

  1. durch individuelle Zuordnung der Beschleuniger Laufzeit oder Treiber des Speicherblocks gemeldet
  2. zusätzlich kann das Betriebssystem sicherstellen, dass die zugewiesenen Speicherblock Seite gesperrt ist (manche nennen das gepinnt Speicher ), das heißt, bewegen Sie den virtuellen Speicher-Subsystem des Betriebssystems kann nicht oder entfernen Sie die Seite innerhalb oder aus dem Speicher
  3. , wenn 1. und 2. Halt und eine Datenübertragung zwischen einer Seite gesperrt Speicherblockes und einem Beschleuniger angefordert wird, kann die Laufzeit direkt auf die Daten im Hauptspeicher zugreifen, da sie weiß, wo es ist, und es kann der Betrieb sicher sein, System nicht bewegen / entfernen es
  4. diese eine Speicherkopie speichert, die mit Speicher, der zugeordnet wurde in einer nicht-Seite gesperrt Art und Weise auftreten würde: müssen die Daten aus dem Beschleuniger im Hauptspeicher auf einer Seite gesperrt Bereitstellungsbereich kopiert werden kann die Datenübertragung initialisieren (durch DMA)

Ich bin mit benutzerdefinierten Verteilern hier; man könnte sogar sagen, es ist arbeiten um andere benutzerdefinierten dynamische Speicherverwaltung.

Hintergrund: Wir haben Überlastungen für malloc, calloc, frei, und die verschiedenen Varianten der Bediener neu und löschen, und der Linker macht glücklich STL verwenden, um diese für uns. Auf diese Weise können wir Dinge tun, wie automatisches Klein Objektpooling, Lecksuche, alloc Füllung freie Füllung, padding Zuordnung mit Wachen, Cache-Line-Ausrichtung für bestimmte Allocs und verzögert frei.

Das Problem ist, sind wir in einer eingebetteten Umgebung laufen - dort um tatsächlich nicht genügend Speicher zur Erkennung lecken ordnungsgemäß über einen längeren Zeitraum entfallen. Zumindest nicht in der Standard-RAM - es gibt einen anderen Haufen RAM an anderer Stelle zur Verfügung, durch individuelle Zuordnungsfunktionen

.

Lösung: eine benutzerdefinierte allocator schreiben, die die erweiterte Heap verwendet, und verwenden Sie es nur in die Interna der Speicherleck-Tracking-Architektur ... Alles andere standardmäßig auf den normalen neu / löschen Überlastungen, die tun Leck-Tracking. Dies vermeidet den Tracker Tracking selbst (und ein bisschen extra Verpackung Funktionalität auch wissen, dass wir die Größe des Tracker-Knoten).

Wir diese auch verwenden, um Funktion Kostenprofildaten, aus dem gleichen Grunde zu halten; einen Eintrag für jeden Funktionsaufruf und Rückkehr zu schreiben, sowie Thread-Schalter, kann teuer schnell. Benutzerdefinierte allocator wieder gibt uns kleinere Allocs in einem größeren Debug-Speicherbereich.

Ich verwende eine benutzerdefinierte allocator die Anzahl der Zuteilungen / Aufheben dieser Zuordnungen in einem Teil meines Programms zum Zählen und Messen, wie lange es dauert. Es gibt auch andere Möglichkeiten, dies erreicht werden könnte, aber diese Methode ist für mich sehr praktisch. Es ist besonders nützlich, dass ich das benutzerdefinierte allocator nur für einen Teil meiner Container verwenden kann.

Eine wesentliche Situation. Wenn das Schreiben von Code, der über Modul (EXE / DLL) Grenzen arbeiten muß, ist es wichtig, Ihre Zuweisungen und Streichungen in nur ein Modul geschehen zu halten

Wo ich in dieser lief war eine Plugin-Architektur unter Windows. Es ist wichtig, dass, zum Beispiel, wenn Sie eine std :: string über die DLL-Grenze passieren, dass alle Umschichtungen der Zeichenfolge aus dem Heap auftreten, wo sie von ihrem Ursprung, nicht die Haufen in der DLL, die * verschieden sein können.

* Es ist komplizierter als dies tatsächlich, als ob Sie sich dynamisch an die CRT verknüpfen dies sowieso funktionieren könnte. Aber wenn jede DLL eine statische Verbindung zum CRT hat Sie geht in einer Welt des Schmerzes, wo Phantomzuordnungsfehler immer wieder auftreten.

Ein Beispiel mir Zeit habe ich verwendet, diese wurde mit sehr begrenzten Ressourcen Embedded-Systemen arbeiten. Lassen Sie uns sagen Sie 2k RAM frei und Ihr Programm hat einige dieser Speicher zu verwenden. Sie speichern müssen sagen 4-5 Sequenzen irgendwo, die nicht auf dem Stapel ist und zusätzlich müssen Sie sehr präzisen Zugang über haben, wo diese Dinge gespeichert zu bekommen, ist dies eine Situation, wo Sie möchten Ihr eigenes allocator schreiben. Die Standardimplementierungen der Speicher fragmentieren kann dies nicht akzeptabel sein könnte, wenn Sie nicht genügend Speicher haben und können Ihr Programm nicht neu starten.

Ein Projekt, das ich arbeite an wurde mit AVR-GCC auf einigen niedrigen Leistungschips. Wir hatten 8 Sequenzen von variabler Länge zu speichern, aber mit einem bekannten Maximum. Die Standard-Bibliothek Implementierung der Speicherverwaltung ist eine dünne Hülle um malloc / frei, die Spur hält, wo mit Gegenständen zu platzieren, indem jedem zugewiesenen Speicherblock mit einem Zeiger auf gerade hinter dem Ende der zugewiesenen Teil des Speichers vorangestellt wird. Wenn ein neues Stück Zuweisung von Speichern hat das Standard-Allocator jedes der Stücke des Speichers zu Fuß über den nächsten Block zu finden, die verfügbar ist, wo die angeforderte Speichergröße passen. Auf einem Desktop-Plattform würde dies für diese paar Dinge sehr schnell sein, aber man muss bedenken, dass einige dieser Mikrocontroller sind sehr langsam und primitiv im Vergleich. Zusätzlich war die Speicherfragmentierung Problem ein massives Problem, das bedeutete, dass wir wirklich keine andere Wahl hatten, einen anderen Ansatz zu nehmen.

Also, was wir taten, war unseren eigenen Speicherpool zu implementieren. Jeder Speicherblock war groß genug, um die größte Sequenz passen wir es brauchen würde. Diese feste Größe Speicherblöcke zugewiesen vor der Zeit und markiert, welche Speicherblöcke derzeit im Einsatz waren. Wir haben dies durch eine 8-Bit-Ganzzahl zu halten, wobei jedes Bit dargestellt wird, wenn ein bestimmte Block verwendet wurde. Wir erkauft Speichernutzung hier für den Versuch zu machen, den gesamten Prozess schneller, was in unserem Fall gerechtfertigt war, da wir diesen Mikrocontroller-Chip nahe daran schieben die maximale Verarbeitungskapazität.

Es gibt eine Reihe von anderen Zeiten, die ich sehen kann eigenes allocator im Rahmen von eingebetteten Systemen, zum Beispiel des Schreiben, wenn der Speicher für die Sequenz nicht im Haupt ram ist wie es häufig der Fall sein könnte, auf diese Plattformen .

Für Shared Memory es wichtig, dass nicht nur den Behälterkopf, sondern auch die darin enthaltenen Daten im gemeinsam genutzten Speicher gespeichert werden.

Das allocator von -Boost :: Inter ist ein gutes Beispiel. Doch wie Sie können lesen hier diese allone nicht, genügt alle STL-Container Shared-Memory-kompatibel (Aufgrund der unterschiedlichen Abbildungsverschiebungen in verschiedenen Prozessen, Zeiger könnte „break“) zu machen.

Obligatorische Link zu Andrei Alexandrescu des CppCon 2015 Vortrag über Verteilern:

https://www.youtube.com/watch?v=LIb3L4vKZ7U

Das Schöne daran ist, dass sie nur Konzeption macht Sie denken, Ideen, wie Sie sie verwenden würde: -)

Vor einiger Zeit fand ich diese Lösung sehr nützlich für mich: Schnell C ++ 11 Allocator für STL-Container . Es beschleunigt leicht STL-Container auf VS2017 (~ 5x) sowie auf GCC bis (~ 7x). Es ist ein besonderer Zweck allocator auf Speicherpool basiert. Es kann mit STL-Containern verwendet werden, nur dank der Mechanismus Sie fordern.

Ich persönlich benutze Loki :: Allocator / SmallObject Speichernutzung für kleine Objekte zu optimieren - es zeigt eine gute Effizienz und befriedigende Leistung, wenn Sie mit moderaten Mengen an wirklich kleine Objekte (1 bis 256 Bytes) zu arbeiten. Es kann bis zu ~ 30-mal effizienter als Standard C ++ neu / Zuordnung löschen, wenn wir über die Zuteilung moderate Mengen von kleinen Objekten in vielen verschiedenen Größen sprechen. Außerdem gibt es eine VC-spezifische Lösung namens „QuickHeap“, bringt es bestmögliche Leistung (zuteilen und ausplanen Operationen nur lesen und schreiben Sie die Adresse des Blocks zugeordnet ist / zurückgekehrt häufen, die jeweils in bis zu 99 (9)% der Fälle - abhängig von Einstellungen und Initialisierung), jedoch auf Kosten einen bemerkenswerten Aufwand - es braucht zwei Zeiger pro Grad und eine extra für jeden neuen Speicherblock. Es ist eine schnellstmögliche Lösung für die Arbeit mit großen (10 000 ++) Mengen von Objekten erstellt und gelöscht werden, wenn Sie nicht über eine große Vielzahl von Objektgrößen benötigen (es schafft eine individuelle Pool für jede Objektgröße, 1-1023 Bytes in der aktuellen Implementierung, so Initialisierung Kosten die Boost-Gesamtleistung schmälern können, aber man kann einige Dummy-Objekte gehen sie vor und zuweisen / Frei, bevor die Anwendung es ist leistungskritische Phase (n)) eintritt.

Das Problem mit dem Standard-C ++ neu / löschen Implementierung ist, dass es für C malloc / free Zuordnung in der Regel nur ein Wrapper ist, und es funktioniert gut für größere Speicherblöcke, wie 1024+ Bytes. Es hat eine bemerkenswerte Overhead in Bezug auf Leistung und manchmal zusätzliche Speicher zu für die Zuordnung verwendet. So Brauch in den meisten Fällen Verteilern in einer Weise umgesetzt werden, um die Leistung und / oder zu minimieren, um die Menge an zusätzlichen Speicher für die Zuteilung von kleinen (≤1024 Bytes) Objekte erforderlich zu maximieren.

In einer Grafik Simulation habe ich benutzerdefinierte Verteilern gesehen verwendet für

  1. Ausrichtung Einschränkungen, die std::allocator nicht direkt unterstützen.
  2. Minimierung der Fragmentierung unter Verwendung von separaten Pools für kurzlebigen (nur diesen Rahmen) und langlebige Zuweisungen.
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top