2012-05-20 3 views
11

Ich habe eine Klasse, die wie folgt aussieht:Gibt es ein Äquivalent von vector :: reserve() für eine std :: list?

typedef std::list<char*> PtrList; 
class Foo 
{ 
public: 
    void DoStuff(); 
private: 
    PtrList m_list; 
    PtrList::iterator m_it; 
}; 

Die Funktion DoStuff() grundsätzlich Elemente m_list hinzufügt oder löscht Elemente daraus, findet einen Iterator zu einem besonderen Element in sie und speichert sie in m_it. Es ist wichtig zu beachten, dass jeder Wert von m_it in jedem folgenden Anruf von DoStuff() verwendet wird.

Also, was ist das Problem? Alles funktioniert, außer dass die Profilerstellung zeigt, dass der Operator new wegen list::push_back() aufgerufen von DoStuff() zu viel aufgerufen wird.

Leistung erhöhen ich für Speicher wollen m_list in der Initialisierung von Foo vorzubelegen, wie ich tun würde, wenn es sich um eine std::vector waren. Das Problem ist, dass diese neue Probleme einführen würde, wie:

  1. Weniger effiziente insert und erase von Elementen.
  2. m_it wird ungültig, sobald der Vektor von einem Aufruf zu DoStuff() zum nächsten geändert wird. EDIT: Alan Stokes vorgeschlagen, einen Index anstelle eines Iterators zu verwenden, dieses Problem zu lösen.

Meine Lösung: die einfachste Lösung, die ich denken könnte, ist ein Pool von Objekten zu implementieren, die auch eine verknüpfte Liste Funktionalität. Auf diese Weise bekomme ich eine verkettete Liste und kann Speicher dafür vorbelegen.

Fehle ich etwas oder ist es wirklich die einfachste Lösung? Ich möchte das Rad nicht "neu erfinden" und stattdessen eine Standardlösung verwenden, falls diese existiert.

Alle Gedanken, Umgehungslösungen oder aufschlussreiche Kommentare würden uns freuen!

+0

Wofür verwenden Sie diese Zeichen * genau? – ScarletAmaranth

+0

@ScarletAmaranth Dies sind Zeiger auf Orte im Speicher, wo Datenpakete gespeichert werden (ich habe es für die Frage auf char * vereinfacht). –

+2

Haben Sie überprüft, ob es tatsächlich besser funktioniert, wenn Sie nur einen 'Vektor' verwenden? Es könnte gut tun; Eine schnelle Zuweisung und ein gutes Cache-Verhalten machen die Einfüge-/Löschkosten oft wett. –

Antwort

6

Ich denke, Sie verwenden den Container falsch.

Wenn Sie schnell zurück drücken möchten, dann nicht automatisch davon ausgehen, dass Sie eine verkettete Liste benötigen, eine verkettete Liste ist ein langsamer Container, es ist grundsätzlich für die Neuordnung geeignet.

Ein besserer Container ist ein Std :: Deque. Eine Deque ist im Grunde eine Anordnung von Arrays. Es reserviert einen Speicherblock und belegt es, wenn Sie zurück drücken, wenn es ausläuft, wird es einen anderen Block zuweisen. Dies bedeutet, dass es nur sehr selten zugewiesen wird und Sie die Größe des Containers nicht rechtzeitig für die Effizienz kennen müssen, wie beispielsweise std :: vector und reserver.

+0

Ich brauche ein schnelles 'Erase' zusätzlich zu einem schnellen' Push_back'. –

+0

Haben Sie tatsächlich das 'Löschen' von' std :: deque' mit dem von 'std :: list' verglichen? 'std :: list' ist im Vergleich zu zusammenhängenden Speichercontainern sehr langsam. Wie groß sind deine Sets? – 111111

+0

Ich habe es nicht bewertet, nein. Meine Sets enthalten ca. 10.000 Elemente. –

0

Könnte möglicherweise list::get_allocator().allocate() verwenden. Afaik, Standardverhalten wäre, Speicher zu erwerben als und aufgrund der nicht-zusammenhängenden Art von list s - daher das Fehlen von reserve() - aber keine wesentlichen Nachteile bei der Verwendung der allocator Methode treten mir sofort. Vorausgesetzt, Sie haben einen nicht kritischen Abschnitt in Ihrem Programm, am Anfang oder was auch immer, können Sie zumindest wählen, den Schaden an diesem Punkt zu nehmen.

0

Liste und Vektor unterscheiden sich vollständig in der Art, wie sie Objekte verwalten.

Vektor konstruiert Elemente in einem zugewiesenen Puffer einer gegebenen Kapazität. Neue Zuweisung erfolgt, wenn die Kapazität erschöpft ist. Listen Sie Elemente nacheinander in einem individuell zugewiesenen Bereich auf.

Vektorelemente verschieben sich, wenn etwas eingefügt/entfernt wird, daher sind Vektorindizes und Elementadressen nicht stabil. Listenelemente werden neu verknüpft, wenn etwas eingefügt/entfernt wird, daher sind Listeniteratoren und Elementadressen stabil.

Eine Möglichkeit, eine Liste so zu gestalten, dass sie sich ähnlich wie ein Vektor verhält, ist das Ersetzen des Standardzuordners (der das System bei jedem Aufruf zuweist) durch ein anderes, das Objekte in größeren Teilen zuordnet Liste, wenn es aufgerufen wird. Dies ist nichts, was die Standardbibliothek standardmäßig bietet.

+0

Sie wollten sagen, dass _list iterators_ stabil sind, oder? –

+0

@EitanT: Welcher Teil von "Dies ist nichts, was die Standardbibliothek standardmäßig bereitstellt." Hast du nicht verstanden? –

+0

@NicolBolas Ich spreche nicht über Standard-Bibliothek selbst, vielleicht gibt es andere "Standard" -Implementierungen für solche Zwecke. Sicherlich bin ich nicht der erste, der Speicher für eine 'std :: list' vorreservieren möchte ... –

2

Mein Rat ist der gleiche wie 111111 's, versuchen Sie, auf deque zu wechseln, bevor Sie einen signifikanten Code schreiben.

Um jedoch direkt Ihre Frage zu beantworten: Sie könnten std::list mit einem benutzerdefinierten Zuordner verwenden. Es ist ein wenig fummelig, und ich werde hier nicht alle Details durcharbeiten, aber der Kern davon ist, dass Sie eine Klasse schreiben, die die Speicherzuweisungsstrategie für Listenknoten darstellt. Die von list zugewiesenen Knoten sind eine kleine implementierungsdefinierte Menge größer als char*, aber alle haben die gleiche Größe, was bedeutet, dass Sie einen optimierten Zuordner nur für diese Größe schreiben können (einen Pool von Speicherblöcken und keinen Pool von Objekten)), und Sie können Funktionen hinzufügen, mit denen Sie jederzeit den gewünschten Speicherplatz im Zuordner reservieren können. Dann kann die Liste schnell freigeben/freigeben. Dies erspart Ihnen, die tatsächliche list Funktionalität neu zu implementieren. Wenn Sie (aus irgendeinem Grund) einen Pool von Objekten mit Listenfunktionalität implementieren, können Sie mit boost::intrusive beginnen. Das könnte auch nützlich sein, wenn Sie Ihren eigenen Zuordner schreiben, um Ihre Liste der freien Blöcke zu verfolgen.

+0

+1: Danke für Ihren Rat. Ich wollte Zeit sparen, also wollte ich noch andere Vorschläge hören, bevor ich beschloss, etwas Kompliziertes zu schreiben. Ich folgte dem Vorschlag von 111111 und wechselte zu "deque". Bis jetzt funktioniert es für mich (hauptsächlich, weil die meisten meiner Einfügungen "Pushbacks" sind und nicht in der Mitte). –

4

Sie können die splice-Funktion in std::list verwenden, um einen Pool zu implementieren. Fügen Sie eine neue Mitgliedsvariable PtrList m_Pool hinzu. Wenn Sie ein neues Objekt hinzufügen möchten und der Pool nicht leer ist, weisen Sie den Wert dem ersten Element im Pool zu und fügen Sie ihn dann in die Liste ein. Um ein Element zu löschen, spleiß es aus der Liste in den Pool.

Aber wenn Sie sich nicht um die Reihenfolge der Elemente kümmern, dann kann ein deque viel schneller sein. Wenn Sie ein Element in der Mitte löschen möchten, kopieren Sie das letzte Element auf das Element, das Sie löschen möchten, und löschen Sie dann das letzte Element.

Verwandte Themen