Was ist besser: Vektorkapazität reservieren, Größe vorgeben oder Schleife zurückschieben?

8

Ich habe eine Funktion, die einen Zeiger auf char-Array und Segmentgröße als Eingabeargumente verwendet und eine andere Funktion aufruft, die ein std::array<std::string> benötigt. Die Idee ist, dass das Eingabe-Char-Array in gleiche Teile "unterteilt" und ein String-Array gebildet wird.

Das Eingabe-char-Array-Format besteht aus mehreren kleineren Arrays (oder Strings) mit bestimmter Größe, die miteinander verknüpft sind. Diese werden nicht als nullterminiert angenommen, obwohl sie es sein könnten. Beispiele für Segmentgröße 5 und Anzahl der Elemente 10:

%Vor%

Die Länge aller Char-Arrays ist 51 (Segment * Elemente + 1). Mein Ziel ist es, die Funktion Ressourcen effizient zu nutzen, vor allem Ausführungszeit.

Da es viele Möglichkeiten gibt, eine Katze zu häuten, habe ich zwei (oder drei) Möglichkeiten, dies anzugehen, und die Frage ist: Was ist "besser"? Damit meine ich schneller und weniger Ressourcenverschwendung. Ich bin kein Profi, also sei geduldig mit mir.

Hier wird values vorallokiert und dann jeder Zeichenfolge ein Wert zugewiesen.

%Vor%

Hier wird der Vektor nicht zugewiesen, aber bei jeder Wiederholung wird eine neue Zeichenfolge hinzugefügt.

%Vor%

Es könnte einen dritten Weg geben, das ist besser. Ich bin nicht so erfahren mit Vektoren, also weiß ich es nicht genau. Machen die Segmentlänge und die Anzahl der Elemente einen Unterschied, wenn sie normalerweise groß (5, 10) oder klein (100, 10000) sind?

Erster Beitrag, großer Fan:)

    
daljaz 25.08.2015, 08:43
quelle

4 Antworten

3

Bessere Leistung wird erreicht, wenn dynamische Neuzuordnung vermieden wird. Versuchen Sie also, den Vektorspeicher groß genug zu halten, um alle Elemente zu empfangen.

Ihre erste Lösung wird effizienter sein, denn wenn nSize größer als die Standard-Vektorkapazität ist, benötigt der zweite eine Neuzuweisung, um alle Elemente speichern zu können.

Wie von Melkon kommentiert, ist reserve noch besser:

%Vor%     
jpo38 25.08.2015, 08:56
quelle
8

Hinzufügen von Elementen zu einem Vektor

Es gibt mehrere Möglichkeiten, Daten zu einem Vektor hinzuzufügen:

  • Erstellen Sie einen leeren Vektor, push_back() -Elemente hinein.
  • Erstellen Sie einen leeren Vektor, ordnen Sie etwas Kapazität mit reserve() , dann push_back() elements zu.
  • Erstellen Sie einen Vektor von n elements, verwenden Sie Indizierung und Kopierzuweisung.
  • Erstellen Sie einen leeren Vektor, emplace_back() -Elemente hinein.
  • Erstellen Sie einen leeren Vektor, ordnen Sie etwas Kapazität mit reserve() , dann emplace_back() elements zu.

Es gibt andere Wege, z.B. Erstellen des Containers mit einem Iteratorpaar oder Füllen mit Standardbibliotheksalgorithmen. Ich werde diese hier nicht berücksichtigen.

Allgemeine Überlegungen

Die folgenden zwei Überlegungen sind wichtig für die folgende Analyse:

  • Vermeiden Sie (Neu-) Zuordnungen: Die Speicherzuweisung ist langsam. Bei der Neuzuweisung wird häufig alles, was sich bereits im Container befindet, an den neuen Speicherort kopiert.
  • Vermeiden Sie unnötige Arbeit: Es ist besser, das gewünschte Element zu konstruieren als standardmäßig zu konstruieren und dann zuzuweisen. Es ist besser, ein Element dort zu konstruieren, wo du es haben willst, als es anderswo zu konstruieren, dann kopiere es.

Andere Faktoren beeinflussen auch die Effizienz der gewählten Lösung, aber dies sind wichtige Faktoren, auf die wir direkte Kontrolle haben. Andere Faktoren können durch das Profilieren Ihres Codes offensichtlich werden.

push_back ()

Jedes push_back() copy - Konstruiert ein Element im Vektor vom Argument zum Aufruf push_back() . Wenn der Vektor size() == capacity() ist, wird eine Neuzuweisung durchgeführt. Dies wird normalerweise (aber möglicherweise nicht immer) die Kapazität verdoppeln und dazu führen, dass alle der vorhandenen Elemente in neuen Speicher kopiert werden.

push_back () mit Vorbelegung

Die Verwendung von reserve() weist genügend Speicherplatz für die Elemente zu, bevor wir beginnen. Es lohnt sich immer, dies zu tun, wenn Sie die Anzahl der Elemente kennen oder schätzen. Wenn Sie raten, sind Überschätzungen besser als unterschätzt.

Der push_back() Aufruf verwendet immer noch den Kopierkonstruktor des Elementtyps, aber es sollte keine Zuweisung geben, da der Platz bereits vergeben ist. Sie zahlen nur die Kosten für eine einzelne Zuweisung während des Aufrufs reserve() . Wenn Sie die vorhandene Kapazität überschreiten, wird push_back() neu zugewiesen, wodurch die Kapazität häufig verdoppelt wird. Aus diesem Grund ist eine Überschätzung für die Größe besser; Es ist weniger wahrscheinlich, dass Sie eine Neuzuweisung erhalten, während Sie mit einer Unterschätzung nicht nur wahrscheinlich neue Zuweisungen vornehmen, sondern auch Speicherplatz verschwenden, der fast doppelt so viel wie nötig zuweist!

Beachten Sie, dass das Verhalten "Doubling-Kapazität" nicht vom Standard angegeben wird. Es handelt sich jedoch um eine allgemeine Implementierung, die die Häufigkeit der Neuzuweisung verringert, wenn push_back() für Datensätze unbekannter Größe verwendet wird.

Indizierung und Elementzuweisung

Hier erstellen wir einen Vektor mit der korrekten Anzahl der standardmäßig konstruierten Elemente und verwenden dann den Kopierzuweisungsoperator, um sie durch die gewünschten Elemente zu ersetzen. Dies hat nur eine Zuweisung, kann aber langsam sein, wenn die Kopierzuweisung etwas kompliziert macht. Dies funktioniert nicht wirklich für Datensätze von unbekannter (oder nur erratener) Größe; Die Indexierung von Elementen ist nur dann sicher, wenn Sie wissen, dass der Index nie size() überschreitet, und Sie müssen auf push_back() zurückgreifen oder die Größe ändern, wenn Sie mehr benötigen. Dies ist keine gute allgemeine Lösung, aber es kann manchmal funktionieren.

emplace_back ()

emplace_back() erstellt das Element direkt mit den Argumenten für den emplace_back() -Aufruf. Dies kann oft schneller sein als das Äquivalent push_back() (aber nicht immer). Es reserviert immer noch im selben Muster wie push_back() , reserviert etwas Kapazität, füllt sie und weist sie neu zu, wenn mehr benötigt wird. Ein Großteil des gleichen Arguments gilt, aber Sie können einige Vorteile durch die Konstruktionsmethode erzielen.

emplace_back () mit Vorbelegung

Dies sollte Ihre Einstiegsstrategie für C ++ 11 oder spätere Codebasen sein. Sie erhalten die emplace_back() Effizienz (wo möglich) und vermeiden wiederholte Zuteilungen. Von den aufgeführten Mechanismen wird erwartet, dass dies in den meisten Fällen am schnellsten ist.

Ein Hinweis auf Effizienz

Effizienz kann auf verschiedene Arten gemessen werden. Die Ausführungszeit ist ein gängiges Maß, aber nicht immer das relevanteste. Allgemeiner Hinweis darüber, welche Strategie zu verwenden ist, basiert auf Erfahrung und im Wesentlichen "im Durchschnitt" der verschiedenen Effekte, um einige vernünftige Aussagen darüber zu geben, was zuerst zu tun ist. Wenn Effizienz für Ihre Anwendung von entscheidender Bedeutung ist, können Sie nur sicher sein, dass Sie den richtigen Ort optimieren, indem Sie Ihren Code profilieren , Änderungen vornehmen und dann profile es erneut zu zeigen, dass die Änderungen die gewünschte Wirkung hatten. Verschiedene Datentypen, Hardware, E / A-Anforderungen usw. können diese Art von Timing beeinflussen, und Sie werden nie wissen, wie diese Effekte in Ihrer speziellen Anwendung kombiniert werden, bis Sie profile .

Beispiel

Live-Beispiel: Ссылка

In diesem Beispiel fülle ich einen Vektor mit 10.000 Strings mit jedem der oben aufgeführten Ansätze. Ich zähle jedes Mal und drucke die Ergebnisse.

Das ist ähnlich wie Ihre Frage, aber nicht identisch. Ihre Ergebnisse werden sich unterscheiden!

Beachten Sie, dass emplace_back() mit reserve() am schnellsten ist, aber die Indizierung & amp; Zuweisung ist auch hier schnell. Dies liegt wahrscheinlich daran, dass std::string ein effizientes swap() hat und sein Standardkonstruktor nicht viel bewirkt. Die anderen Ansätze sind eine Größenordnung langsamer.

%Vor%

Ergebnisse:

%Vor%

Schlussfolgerungen

Bei den meisten neuen Codes sollte die Verwendung von reserve() und emplace_back() (oder push_back() für älteren Code) eine gute erste Annäherung für die Effizienz geben. Wenn es nicht genug ist, profilieren Sie es und finden Sie heraus, wo der Engpass ist. Es wird wahrscheinlich nicht sein, wo du denkst, dass es ist.

    
Andrew 25.08.2015 09:37
quelle
3

Verwenden Sie keine Klammern, um den Standardkonstruktor aufzurufen.

push_back erfordert bei jeder Überschreitung der Kapazität zusätzliche Neuzuweisungen. Daher kann Option 2 verbessert werden, indem ausreichend Speicherplatz reserviert wird, um Neuzuweisungen zu vermeiden. Es ist auch effizienter, die Saite direkt zu drücken, als eine leere Saite zu schieben und später erneut zuzuweisen. Und es gibt einen Konstruktor für std::string , der sehr praktisch für Ihre Bedürfnisse ist: aus der Sequenz (5) string (const char* s, size_t n);

Zu Option 1: Vorabzuordnen des gesamten Vektors erfordert, dass jedes Element einmal für die Initialisierung und noch einmal für die Zuweisung konstruiert wird. Besser reservieren, ohne Elemente zu konstruieren und direkt push_back , die Sie wirklich wollen.

Dies ist der Code, der diese Verbesserungen verwendet:

%Vor%     
Jose Antonio Dura Olmos 25.08.2015 09:18
quelle
-2

Machen Sie einfach, was leichter zu lesen und zu pflegen ist. Oft ist es sowieso die schnellste Lösung.

Und selbst wenn es nicht der schnellste ist, wen interessiert das? Vielleicht ist deine App 1% langsamer.

    
Aaron McDaid 25.08.2015 08:51
quelle

Tags und Links