web-dev-qa-db-de.com

Was ist die Leistungsdifferenz, wenn Sie Arrays oder std :: Vectors in C++ verwenden?

In unserem C++ - Kurs wird empfohlen, bei neuen Projekten keine C++ - Arrays mehr zu verwenden. Soweit ich weiß, schlägt Stroustroup vor, keine Arrays zu verwenden. Aber gibt es erhebliche Leistungsunterschiede?

182
tunnuz

Die Verwendung von C++ - Arrays mit new (d. H. Mit dynamischen Arrays) sollte vermieden werden. Es gibt das Problem, dass Sie die Größe nachverfolgen müssen, und Sie müssen sie manuell löschen und alle Arten von Housekeeping durchführen. 

Es wird auch davon abgeraten, Arrays auf dem Stack zu verwenden, da keine Bereichsüberprüfung vorliegt. Wenn Sie das Array herumführen, gehen alle Informationen über seine Größe verloren (Konvertierung von Array zu Zeiger). Sie sollten in diesem Fall boost::array verwenden, der ein C++ - Array in eine kleine Klasse einschließt und eine size-Funktion und Iteratoren bereitstellt, um darüber zu iterieren.

Nun die std :: vector vs. native C++ - Arrays (aus dem Internet):

// Comparison of Assembly code generated for basic indexing, dereferencing, 
// and increment operations on vectors and arrays/pointers.

// Assembly code was generated by gcc 4.1.0 invoked with  g++ -O3 -S  on a 
// x86_64-suse-linux machine.

#include <vector>

struct S
{
  int padding;

  std::vector<int> v;
  int * p;
  std::vector<int>::iterator i;
};

int pointer_index (S & s) { return s.p[3]; }
  // movq    32(%rdi), %rax
  // movl    12(%rax), %eax
  // ret

int vector_index (S & s) { return s.v[3]; }
  // movq    8(%rdi), %rax
  // movl    12(%rax), %eax
  // ret

// Conclusion: Indexing a vector is the same damn thing as indexing a pointer.

int pointer_deref (S & s) { return *s.p; }
  // movq    32(%rdi), %rax
  // movl    (%rax), %eax
  // ret

int iterator_deref (S & s) { return *s.i; }
  // movq    40(%rdi), %rax
  // movl    (%rax), %eax
  // ret

// Conclusion: Dereferencing a vector iterator is the same damn thing 
// as dereferencing a pointer.

void pointer_increment (S & s) { ++s.p; }
  // addq    $4, 32(%rdi)
  // ret

void iterator_increment (S & s) { ++s.i; }
  // addq    $4, 40(%rdi)
  // ret

// Conclusion: Incrementing a vector iterator is the same damn thing as 
// incrementing a pointer.

Hinweis: Wenn Sie Arrays mit new und nicht-Klassenobjekten (wie plain int) oder Klassen ohne benutzerdefinierten Konstruktor und zuordnen, möchten Sie, dass Ihre Elemente nicht initialisiert werden, indem Sie new- zugeordnete Arrays verwenden Leistungsvorteile, da std::vector alle Elemente mit Standardwerten (zum Beispiel 0 für int) auf der Konstruktion initialisiert (Credits an @bernie für das Erinnern an mich). 

181

Präambel für Mikrooptimierer

Merken:

"Programmierer verschwenden eine enorme Menge an Zeit, um über die Geschwindigkeit unkritischer Teile ihrer Programme nachzudenken oder sich darüber Sorgen zu machen. Diese Effizienzversuche wirken sich tatsächlich stark auf die Debugging- und Wartungs-Betrachtung aus 97% der Zeit: vorzeitige Optimierung ist die Wurzel allen Übels. Aber wir sollten unsere Chancen in diesen kritischen 3% nicht aufgeben ".

(Danke an metamorphosis für das vollständige Zitat)

Verwenden Sie kein C-Array anstelle eines Vektors (oder was auch immer), nur weil Sie der Meinung sind, dass es schneller ist, da es niedriger sein sollte. Du wärst falsch.

Verwenden Sie standardmäßig vector (oder den sicheren Container, der an Ihre Bedürfnisse angepasst ist). Wenn Ihr Profiler dies als Problem bezeichnet, prüfen Sie, ob Sie ihn optimieren können, indem Sie entweder einen besseren Algorithmus verwenden oder den Container ändern.

Wir können auf die ursprüngliche Frage zurückkommen.

Statisches/dynamisches Array?

Die C++ - Array-Klassen verhalten sich besser als das Low-Level-C-Array, da sie viel über sich selbst wissen und Fragen beantworten können, die C-Arrays nicht beantworten können. Sie können nach sich selbst reinigen. Und was noch wichtiger ist, sie werden normalerweise unter Verwendung von Vorlagen und/oder Inlining geschrieben. Das bedeutet, dass, was in Debug zu viel Code erscheint, nur wenig oder keinen Code erzeugt, der im Release-Build erzeugt wurde. Dies bedeutet keinen Unterschied zu seinem eingebauten, weniger sicheren Wettbewerb.

Alles in allem fallen zwei Kategorien an:

Dynamische Arrays

Die Verwendung eines Zeigers auf ein malloc-ed/new-ed-Array ist bestenfalls so schnell wie die Version von std :: vector und weniger sicher (siehe litb's post ).

Verwenden Sie also einen std :: vector.

Statische Arrays

Die Verwendung eines statischen Arrays ist am besten:

  • so schnell wie die std :: array version
  • und viel weniger sicher.

Verwenden Sie also ein std :: array .

Nicht initialisierter Speicher

Die Verwendung einer vector anstelle eines rohen Puffers verursacht manchmal sichtbare Kosten, da die vector den Puffer bei der Konstruktion initialisiert, während der Code, den er ersetzt, dies nicht tat, wie bernie von answer bemerkt.

Wenn dies der Fall ist, können Sie damit umgehen, indem Sie einen unique_ptr anstelle einer vector verwenden. Wenn der Fall in Ihrer Codeline nicht außergewöhnlich ist, schreiben Sie tatsächlich eine Klasse buffer_owner, die diesen Speicher besitzen wird, und geben Ihnen einen einfachen und sicheren Zugriff Es beinhaltet Boni wie die Größenänderung (mit realloc?) oder was auch immer Sie brauchen.

64
paercebal

Vektoren sind Arrays unter der Haube. .__ Die Leistung ist gleich.

Ein Punkt, an dem Sie auf ein Leistungsproblem stoßen können, ist, die Größe des Vektors nicht korrekt zu skalieren.

Wenn sich ein Vektor füllt, ändert sich die Größe des Vektors selbst, und dies kann eine neue Arrayzuweisung implizieren, gefolgt von n Kopierkonstruktoren, gefolgt von etwa n Destruktoraufrufen, gefolgt von einem Array löschen.

Wenn Ihr Konstrukt/Destrukt teuer ist, ist es viel besser, wenn Sie den Vektor zunächst auf die richtige Größe einstellen.

Es gibt eine einfache Möglichkeit, dies zu demonstrieren. Erstellen Sie eine einfache Klasse, die anzeigt, wann sie erstellt/zerstört/kopiert/zugewiesen wird. Erstellen Sie einen Vektor dieser Dinge und schieben Sie sie an das hintere Ende des Vektors. Wenn sich der Vektor füllt, kommt es zu einer Aktivitätskaskade, wenn der Vektor seine Größe ändert. Versuchen Sie es dann erneut mit dem Vektor, der auf die erwartete Anzahl von Elementen eingestellt ist. Sie werden den Unterschied sehen.

29
EvilTeach

Um auf etwas zu antworten Mehrdad sagte:

Es kann jedoch Fälle geben, in denen Sie brauchen immer noch Arrays. Wann Schnittstelle mit Low-Level-Code (d. h Assembly) oder alten Bibliotheken, die Arrays erfordern, sind Sie möglicherweise nicht in der Lage Vektoren verwenden.

Überhaupt nicht wahr. Vektoren lassen sich gut in Arrays/Zeiger zerlegen, wenn Sie Folgendes verwenden:

vector<double> vector;
vector.Push_back(42);

double *array = &(*vector.begin());

// pass the array to whatever low-level code you have

Dies funktioniert für alle wichtigen STL-Implementierungen. In der nächsten Norm wird es erforderlich sein zu arbeiten (auch wenn es heute gut geht).

24
Frank Krueger

Es gibt noch weniger Gründe, reine Arrays in C++ 11 zu verwenden.

Es gibt drei Arten von Arrays in der Natur, von den schnellsten bis zu den langsamsten, abhängig von den Features, die sie haben (natürlich kann die Qualität der Implementierung sogar für Fall 3 in der Liste sehr schnell werden):

  1. Statisch mit bekannter Größe zur Kompilierzeit. --- std::array<T, N>
  2. Dynamisch mit einer zur Laufzeit bekannten Größe, deren Größe jedoch nie geändert wurde. Die typische Optimierung ist hier, dass das Array direkt im Stack zugeordnet werden kann. -- Nicht verfügbar. Möglicherweise dynarray in C++ TS nach C++ 14. In C gibt es VLAs 
  3. Dynamisch und zur Laufzeit skalierbar. --- std::vector<T>

Verwenden Sie für 1. einfache statische Arrays mit einer festen Anzahl von Elementen std::array<T, N> in C++ 11.

Für 2. Arrays mit fester Größe, die zur Laufzeit angegeben wurden, deren Größe sich jedoch nicht ändert, wird in C++ 14 diskutiert, aber es wurde in eine technische Spezifikation verschoben und aus C++ 14 herausgegeben endlich.

Für 3.std::vector<T>fragt normalerweise nach Speicher im Heap. Dies kann sich auf die Leistung auswirken, obwohl Sie std::vector<T, MyAlloc<T>> verwenden könnten, um die Situation mit einem benutzerdefinierten Zuweiser zu verbessern. Der Vorteil gegenüber T mytype[] = new MyType[n]; ist, dass Sie die Größe ändern können und nicht wie bei einfachen Arrays in einen Zeiger zerfallen.

Verwenden Sie die genannten Standardbibliothekstypen, um zu vermeiden, dass Arrays auf Zeiger zerfallen. Sie sparen Debugging-Zeit und die Leistung ist genau die gleiche wie bei einfachen Arrays, wenn Sie die gleichen Features verwenden.

14
Germán Diago

Gehen Sie mit STL. Es gibt keine Leistungsstrafe. Die Algorithmen sind sehr effizient und bewältigen die Details, über die die meisten von uns nicht nachdenken würden.

6
John D. Cook

Über den Beitrag von Duli

Die Schlussfolgerung ist, dass Arrays von Ganzzahlen schneller sind als Vektoren von Ganzzahlen (in meinem Beispiel fünfmal). Arrays und Vektoren haben jedoch die gleiche Geschwindigkeit für komplexere/nicht ausgerichtete Daten.

5
lalebarde

STL ist eine stark optimierte Bibliothek. Tatsächlich wird sogar empfohlen, STL in Spielen zu verwenden, in denen möglicherweise eine hohe Leistung erforderlich ist. Arrays sind zu fehleranfällig, um in täglichen Aufgaben verwendet zu werden. Die heutigen Compiler sind auch sehr intelligent und können mit STL wirklich ausgezeichneten Code erzeugen. Wenn Sie wissen, was Sie tun, kann STL normalerweise die erforderliche Leistung erbringen. Wenn Sie zum Beispiel Vektoren auf die erforderliche Größe initialisieren (wenn Sie es von Anfang an wissen), können Sie im Grunde die Array-Leistung erreichen. Es kann jedoch Fälle geben, in denen Sie noch Arrays benötigen. Wenn Sie mit Low-Level-Code (d. H. Assembly) oder alten Bibliotheken, für die Arrays erforderlich sind, eine Schnittstelle herstellen, können Sie möglicherweise keine Vektoren verwenden. 

5
Mehrdad Afshari

Wenn Sie die Software im Debug-Modus kompilieren, werden viele Compiler die Accessor-Funktionen des Vektors nicht integrieren. Dies führt dazu, dass die Implementierung des stl-Vektors unter Umständen, in denen die Leistung ein Problem ist, viel langsamer ist. Dadurch wird auch der Code einfacher zu debuggen, da Sie im Debugger sehen können, wie viel Speicherplatz zugewiesen wurde.

Im optimierten Modus würde ich erwarten, dass sich der stl-Vektor der Effizienz eines Arrays annähert. Dies ist darauf zurückzuführen, dass viele Vektormethoden jetzt inline sind.

3
Juan

Wenn Sie einen nicht initialisiert -Puffer (z. B. als Ziel für memcpy()) verwenden möchten, wirkt sich dies definitiv auf die Leistung eines std::vector gegenüber einem rohen Array aus. Ein std::vector initialisiert alle Elemente mit dem Standardkonstruktor. Ein Raw-Array wird dies nicht tun.

Die c ++ - Spezifikation für den std:vector-Konstruktor, der ein count-Argument (es ist die dritte Form) verwendet, besagt:

`Konstruiert einen neuen Container aus einer Vielzahl von Datenquellen, wobei optional eine vom Benutzer bereitgestellte Zuweiserzuweisung verwendet wird.

3) Konstruiert den Container mit der Anzahl der standardmäßig vorbelegten Anzahl von T. Es werden keine Kopien erstellt.

Komplexität

2-3) Linear in der Zählung

Ein Roh-Array verursacht diese Initialisierungskosten nicht.

Siehe auch Wie kann ich vermeiden, dass std :: vector <> alle seine Elemente initialisiert?

3
bernie

Der Leistungsunterschied zwischen den beiden hängt stark von der Implementierung ab. Wenn Sie einen schlecht implementierten std :: vector mit einer optimalen Arrayimplementierung vergleichen, würde das Array gewinnen, aber drehen Sie es um und der Vektor würde gewinnen.

Solange Sie Äpfel mit Äpfeln vergleichen (entweder haben das Array und der Vektor eine feste Anzahl von Elementen oder werden beide dynamisch skaliert), würde ich denken, dass der Leistungsunterschied vernachlässigbar ist, solange Sie die STL-Codierungspraxis befolgen. Vergessen Sie nicht, dass Sie bei Verwendung von Standard-C++ - Containern auch die vorgerollten Algorithmen verwenden können, die Teil der Standard-C++ - Bibliothek sind. Die meisten von ihnen sind wahrscheinlich leistungsfähiger als die durchschnittliche Implementierung des gleichen Algorithmus, den Sie selbst erstellen .

IMHO gewinnt der Vektor in einem Debug-Szenario mit einer Debug-STL, da die meisten STL-Implementierungen mit einem geeigneten Debug-Modus die typischen Fehler hervorheben können, die von Benutzern bei der Arbeit mit Standardcontainern begangen werden.

Und vergessen Sie nicht, dass das Array und der Vektor das gleiche Speicherlayout haben, sodass Sie Vektoren verwenden können, um Daten an C/C-Legacy-Code zu übergeben, der grundlegende Arrays erwartet. Denken Sie jedoch daran, dass die meisten Wetten in diesem Szenario nicht verfügbar sind und Sie sich wieder mit rohem Speicher beschäftigen.

2
Timo Geusch

Der folgende einfache Test:

C++ Array vs. Vector Leistungstest Erklärung

widerspricht den Schlussfolgerungen aus "Vergleich von Assemblycode, der für grundlegende Indizierungs-, Dereferenzierungs- und Inkrementierungsoperationen für Vektoren und Arrays/Zeiger erzeugt wird". 

Es muss ein Unterschied zwischen den Arrays und Vektoren bestehen. Der Test sagt es so ... probier es einfach aus, der Code ist da ...

1
Hamed100101

In einigen Fällen gibt es einen Edge-Fall, bei dem Sie einen Vektorzugriff innerhalb einer Inline-Funktion innerhalb einer Inline-Funktion haben, bei dem Sie über das hinausgehen, was der Compiler inline tun wird. Das wäre so selten, dass es sich nicht lohnt, sich darüber Gedanken zu machen - im Allgemeinen stimme ich mit litb überein.

Ich bin überrascht, dass dies noch niemand erwähnt hat - machen Sie sich keine Sorgen über die Leistung, bis sich herausgestellt hat, dass dies ein Problem ist.

1
Mark Ransom

Vektoren benötigen etwas mehr Speicher als Arrays, da sie die Größe des Arrays enthalten. Sie erhöhen auch die Festplattengröße von Programmen und möglicherweise den Speicherbedarf von Programmen. Diese Erhöhungen sind winzig, können jedoch von Bedeutung sein, wenn Sie mit einem eingebetteten System arbeiten. An den meisten Stellen, an denen diese Unterschiede wichtig sind, handelt es sich jedoch um Orte, an denen Sie C anstelle von C++ verwenden würden.

1
Brian

Manchmal sind Arrays tatsächlich besser als Vektoren. Wenn Sie immer eine feste Länge von Objekten bearbeiten, sind Arrays besser. Betrachten Sie die folgenden Codeausschnitte:

int main() {
int v[3];
v[0]=1; v[1]=2;v[2]=3;
int sum;
int starttime=time(NULL);
cout << starttime << endl;
for (int i=0;i<50000;i++)
for (int j=0;j<10000;j++) {
X x(v);
sum+=x.first();
}
int endtime=time(NULL);
cout << endtime << endl;
cout << endtime - starttime << endl;

}

wo die Vektorversion von X ist

class X {
vector<int> vec;
public:
X(const vector<int>& v) {vec = v;}
int first() { return vec[0];}
};

und die Array-Version von X ist:

class X {
int f[3];

public:
X(int a[]) {f[0]=a[0]; f[1]=a[1];f[2]=a[2];}
int first() { return f[0];}
};

Die Array-Version von main () wird schneller sein, da wir in der inneren Schleife jedes Mal den

(Dieser Code wurde von mir in comp.lang.c ++ gepostet).

1
duli

Ich würde behaupten, dass die Hauptsorge nicht die Leistung, sondern die Sicherheit ist. Sie können mit Arrays viele Fehler machen (z. B. eine Größenänderung in Betracht ziehen), bei denen ein Vektor Ihnen viel Schmerz ersparen würde.

1

Wenn Sie Vektoren verwenden, um mehrdimensionales Verhalten darzustellen, ist die Leistung beeinträchtigt.

Verursachen 2d + Vektoren einen Leistungseinbruch?

Das Wesentliche ist, dass jeder Teilvektor einen geringen Overhead mit Größeninformationen hat und nicht unbedingt eine Serialisierung von Daten erfolgt (wie dies bei mehrdimensionalen c-Arrays der Fall ist). Dieser Mangel an Serialisierung kann mehr als nur Möglichkeiten zur Mikrooptimierung bieten. Wenn Sie mehrdimensionale Arrays erstellen, ist es möglicherweise am besten, std :: vector zu erweitern und Ihre eigene Funktion zum Abrufen/Setzen/Ändern der Größe von Bits zu verwenden.

1
Seph Reed

Wenn Sie die Größe nicht dynamisch anpassen müssen, haben Sie den Speicheraufwand, um die Kapazität zu sparen (ein Zeiger/size_t). Das ist es.

1
Greg Rogers

Angenommen, ein Array mit fester Länge (z. B. int* v = new int[1000]; vs std::vector<int> v(1000);, wobei die Größe von v auf 1000 festgelegt ist), ist die Geschwindigkeit von nur die Leistung, die wirklich wichtig ist (oder zumindest, wenn ich in einem ähnlichen Dilemma war) Zugriff auf ein Element. Ich habe den Vektorcode der STL nachgeschlagen, und ich habe folgendes gefunden:

const_reference
operator[](size_type __n) const
{ return *(this->_M_impl._M_start + __n); }

Diese Funktion wird vom Compiler höchstwahrscheinlich inliniert. Solange das einzige, was Sie mit v tun wollen, der Zugriff auf die Elemente mit operator[] ist, scheint es, als sollte es keinen Unterschied in der Leistung geben.

0
Subh_b