Warum würde ich es vorziehen, Vektor zum Deque zu verwenden?

86

Schon seit

  1. sie sind beide zusammenhängende Speichercontainer;
  2. In Bezug auf die Funktionen hat deque fast alles, was der Vektor hat, aber mehr, da es effizienter ist, vorne einzufügen.

Warum whould jemand lieber std::vectorzu std::deque?

Leon
quelle
2
Die Visual C ++ - Implementierung von std::dequehat eine sehr kleine maximale Blockgröße (~ 16 Bytes, wenn ich mich richtig erinnere; vielleicht 32) und ist daher für realistische Anwendungen nicht sehr gut geeignet. A deque<T>where sizeof(T) > 8(oder 16? Es ist eine kleine Zahl) hat ungefähr die gleichen Leistungsmerkmale wie a vector<T*>, bei dem jedes Element dynamisch zugewiesen wird. Andere Implementierungen haben unterschiedliche maximale Blockgrößen, sodass das Schreiben von Code mit relativ gleichen Leistungsmerkmalen auf verschiedenen Plattformen schwierig ist deque.
James McNellis
12
Deque ist kein kontinuierlicher Speichercontainer.
Mohamed El-Nakib
@ravil Nein, das ist das Duplikat, das auf diese Frage zeigt.
1
Kaum zu glauben, dass eine Frage mit einem nicht behobenen offensichtlichen sachlichen Fehler bei einem Saldo von 34 Stimmen lag
underscore_d
2
@underscore_d deshalb ist es eine Frage. Andere Geschichte, wenn es eine Antwort war;)
Assimilater

Antworten:

115

Elemente in a dequesind im Gedächtnis nicht zusammenhängend; vectorElemente sind garantiert. Wenn Sie also mit einer einfachen C-Bibliothek interagieren müssen, die zusammenhängende Arrays benötigt, oder wenn Sie sich (viel) für die räumliche Lokalität interessieren, bevorzugen Sie möglicherweise vector. Da es eine zusätzliche Buchhaltung gibt, sind andere vectorOperationen wahrscheinlich (etwas) teurer als ihre entsprechenden Operationen. Andererseits kann die Verwendung vieler / großer Instanzen von vectorzu einer unnötigen Heap-Fragmentierung führen (Verlangsamung der Aufrufe vonnew ).

Wie bereits an anderer Stelle in StackOverflow erwähnt , gibt es hier weitere gute Diskussionen: http://www.gotw.ca/gotw/054.htm .

Phooji
quelle
3
Es scheint, dass der Link zu "anderswo" jetzt tot ist (aufgrund von Mäßigung?).
Esilk
37

Um den Unterschied zu kennen, sollte man wissen, wie dequeallgemein implementiert wird. Der Speicher wird in gleich großen Blöcken zugewiesen und miteinander verkettet (als Array oder möglicherweise als Vektor).

Um das n-te Element zu finden, finden Sie den entsprechenden Block und greifen dann auf das darin enthaltene Element zu. Dies ist eine konstante Zeit, da es immer genau 2 Suchvorgänge sind, aber das ist immer noch mehr als der Vektor.

vectorfunktioniert auch gut mit APIs, die einen zusammenhängenden Puffer benötigen, da sie entweder C-APIs sind oder vielseitiger darin sind, einen Zeiger und eine Länge zu verwenden. (So ​​können Sie einen Vektor darunter oder ein reguläres Array haben und die API aus Ihrem Speicherblock aufrufen).

Wo dequehat seine größten Vorteile:

  1. Beim Wachsen oder Schrumpfen der Sammlung an beiden Enden
  2. Wenn Sie es mit sehr großen Sammlungsgrößen zu tun haben.
  3. Wenn es um Bools geht und Sie wirklich eher Bools als ein Bitset wollen.

Die zweite davon ist weniger bekannt, aber für sehr große Sammlungsgrößen:

  1. Die Kosten für die Neuzuweisung sind hoch
  2. Der Aufwand, einen zusammenhängenden Speicherblock finden zu müssen, ist begrenzt, sodass Ihnen schneller der Speicher ausgeht.

Als ich in der Vergangenheit mit großen Sammlungen zu tun hatte und von einem zusammenhängenden Modell zu einem Blockmodell wechselte, konnten wir eine etwa fünfmal so große Sammlung speichern, bevor uns der Speicher in einem 32-Bit-System ausging. Dies liegt zum Teil daran, dass beim erneuten Zuweisen sowohl der alte als auch der neue Block gespeichert werden mussten, bevor die Elemente kopiert wurden.

Nach alledem können Probleme mit std::dequeSystemen auftreten, die eine "optimistische" Speicherzuordnung verwenden. Während seine Versuche, eine große Puffergröße für eine Neuzuweisung von a anzufordern, vectorwahrscheinlich irgendwann mit a abgelehnt werden bad_alloc, wird die optimistische Natur des Allokators wahrscheinlich immer die Anforderung für den kleineren Puffer gewähren, der von a angefordert wird, dequeund dies wird wahrscheinlich dazu führen das Betriebssystem, um einen Prozess abzubrechen, um zu versuchen, etwas Speicher zu erhalten. Welches auch immer es auswählt, es ist vielleicht nicht zu angenehm.

Die Problemumgehungen in einem solchen Fall bestehen entweder darin, Flags auf Systemebene zu setzen, um die optimistische Zuordnung zu überschreiben (was nicht immer möglich ist), oder den Speicher etwas manueller zu verwalten, z. B. mithilfe Ihres eigenen Allokators, der die Speichernutzung überprüft, oder ähnlichem. Offensichtlich nicht ideal. (Welches kann Ihre Frage beantworten, um Vektor zu bevorzugen ...)

Goldesel
quelle
29

Ich habe sowohl Vektor als auch Deque mehrmals implementiert. deque ist aus Sicht der Implementierung sehr viel komplizierter. Diese Komplikation führt zu mehr Code und komplexerem Code. Daher wird normalerweise ein Treffer in der Codegröße angezeigt, wenn Sie Deque anstelle von Vektor auswählen. Es kann auch zu einem kleinen Geschwindigkeitstreffer kommen, wenn Ihr Code nur die Dinge verwendet, bei denen sich der Vektor auszeichnet (dh push_back).

Wenn Sie eine Warteschlange mit zwei Enden benötigen, ist deque der klare Gewinner. Wenn Sie jedoch die meisten Einfügungen und Löschungen auf der Rückseite vornehmen, wird der Vektor der klare Gewinner sein. Wenn Sie sich nicht sicher sind, deklarieren Sie Ihren Container mit einem typedef (damit Sie leicht hin und her wechseln können) und messen Sie.

Howard Hinnant
quelle
3
Frage - Hat das Komitee erwogen, C ++ einen Hybrid aus beiden (z. B. ein "Deck") hinzuzufügen? (dh ein Double-Ended vector.) Ich habe in meiner Antwort eine Implementierung geschrieben, auf die unten verwiesen wird . Es kann so schnell sein wie eine, vectoraber viel breitere Anwendung (z. B. wenn eine schnelle Warteschlange erstellt wird).
user541686
5

std::dequehat keinen garantierten kontinuierlichen Speicher - und ist für den indizierten Zugriff oft etwas langsamer. Eine Deque wird typischerweise als "Liste von Vektoren" implementiert.

Erik
quelle
14
Ich denke nicht, dass eine "Liste von Vektoren" korrekt ist: Mein Verständnis war, dass die meisten Implementierungen ein "Vektor von Zeigern auf Arrays" waren, obwohl dies von Ihrer Definition von "Liste" abhängt (ich lese "Liste" als "verknüpfte Liste" , "was die Komplexitätsanforderungen nicht erfüllen würde.)
James McNellis
2

Laut http://www.cplusplus.com/reference/stl/deque/ "ist es im Gegensatz zu Vektoren nicht garantiert, dass Deques alle ihre Elemente in zusammenhängenden Speicherorten haben, wodurch die Möglichkeit eines sicheren Zugriffs durch Zeigerarithmetik ausgeschlossen wird."

Deques sind etwas komplizierter, zum Teil, weil sie nicht unbedingt ein zusammenhängendes Speicherlayout haben. Wenn Sie diese Funktion benötigen, sollten Sie keine Deque verwenden.

(Zuvor hatte meine Antwort einen Mangel an Standardisierung angesprochen (aus derselben Quelle wie oben, "Deques können von bestimmten Bibliotheken auf unterschiedliche Weise implementiert werden"), aber das gilt tatsächlich für nahezu jeden Standardbibliotheksdatentyp.)

patrickvacek
quelle
5
std::dequeist nicht weniger standardisiert als std::vector. Ich glaube nicht, dass die Komplexitätsanforderungen für std::dequedurch zusammenhängenden Speicher erfüllt werden können.
Jerry Coffin
1
Vielleicht war meine Formulierung schlecht: Obwohl die Standardisierung meines Erachtens nicht gründlich ist, werden Vektoren standardisiert, um eine konitugöse Sequenz zu sein, und Deques nicht. Das scheint der entscheidende Faktor zu sein.
Patrickvacek
1
@ JerryCoffin: Welche Komplexitätsanforderungen von dequekönnen mit zusammenhängendem Speicher nicht erfüllt werden?
user541686
1
@Mehrdad: Um ehrlich zu sein, ich erinnere mich nicht, was ich vorhatte. Ich habe diesen Teil des Standards in letzter Zeit nicht genug angeschaut, so dass ich mich wohl fühle, wenn ich kategorisch feststelle, dass mein früherer Kommentar falsch war, aber wenn ich ihn mir jetzt anschaue, kann ich mir auch nicht vorstellen, wie er richtig wäre.
Jerry Coffin
3
@JerryCoffin: Die Komplexitätsanforderungen sind eigentlich trivial: Sie könnten ein großes Array zuweisen und Ihre Sequenz von der Mitte nach außen verschieben (ich denke, dies ist die Aufgabe der Mehrdad-Implementierung) und dann neu zuweisen, wenn Sie am Ende angelangt sind. Das Problem bei diesem Ansatz besteht darin, dass er eine der Anforderungen von nicht erfüllt deque, nämlich dass das Einfügen an den Enden die Bezugnahme auf die vorhandenen Elemente nicht ungültig macht . Diese Anforderung impliziert einen diskontinuierlichen Speicher.
Yakov Galka
0

Ein Deque ist ein Sequenzcontainer, der den wahlfreien Zugriff auf seine Elemente ermöglicht, für den jedoch keine zusammenhängende Speicherung garantiert ist.

Sean
quelle
0

Ich denke, diese gute Idee, einen Leistungstest für jeden Fall durchzuführen. Und treffen Sie Ihre Entscheidung anhand dieser Tests.

Ich würde es vorziehen std::dequeals std::vectorin den meisten Fällen.

George Gaál
quelle
1
Die Frage, ob aus den sachlichen Fehlern und fehlenden Worten etwas herausgearbeitet werden kann, war, warum jemand es vorziehen würde vector. Wir können daraus schließen, dass warum nicht eine Konsequenz ist. Zu sagen, dass Sie dequeaus unbekannten Gründen nicht spezifizierte Tests bevorzugen , ist keine Antwort.
underscore_d
0

Einerseits ist der Vektor ziemlich häufig einfach schneller als die Deque. Wenn Sie nicht alle Funktionen von deque benötigen , verwenden Sie einen Vektor.

Auf der anderen Seite, manchmal Sie tun müssen Funktionen , die Vektor Ihnen nicht geben, in dem Fall , dass Sie eine deque verwenden. Zum Beispiel fordere ich jeden auf, zu versuchen, diesen Code neu zu schreiben , ohne eine Deque zu verwenden und ohne den Algorithmus enorm zu verändern.

BenGoldberg
quelle
Tatsächlich ist bei derselben Serie von push_backund pop_backOperationen deque<int>immer mindestens 20% schneller als vector<int>in meinen Tests (gcc mit O3). Ich denke, deshalb dequeist die Standardwahl für Dinge wie std::stack...
igel
-1

Beachten Sie, dass der Vektorspeicher neu zugewiesen wird, wenn das Array wächst. Wenn Sie Zeiger auf Vektorelemente haben, werden diese ungültig.

Wenn Sie ein Element löschen, werden Iteratoren ungültig (jedoch nicht "für (auto ...)").

Bearbeiten: 'deque' in 'vector' geändert

Pierre
quelle
-1: Einfügen und Löschen am Anfang oder Ende macht Verweise und Zeiger auf andere Elemente NICHT ungültig. (Obwohl das Einfügen und Löschen in der Mitte der Fall ist)
Sjoerd
@Sjoerd Ich habe es in 'vector' geändert.
Pierre