Ich habe immer gedacht, dass es die allgemeine Weisheit std::vector
ist, die "als Array implementiert" ist, bla bla bla. Heute bin ich runtergegangen und habe es getestet, und es scheint nicht so zu sein:
Hier sind einige Testergebnisse:
UseArray completed in 2.619 seconds
UseVector completed in 9.284 seconds
UseVectorPushBack completed in 14.669 seconds
The whole thing completed in 26.591 seconds
Das ist ungefähr 3 - 4 mal langsamer! Rechtfertigt nicht wirklich die vector
Kommentare " Kann für ein paar Nanosekunden langsamer sein".
Und der Code, den ich verwendet habe:
#include <cstdlib>
#include <vector>
#include <iostream>
#include <string>
#include <boost/date_time/posix_time/ptime.hpp>
#include <boost/date_time/microsec_time_clock.hpp>
class TestTimer
{
public:
TestTimer(const std::string & name) : name(name),
start(boost::date_time::microsec_clock<boost::posix_time::ptime>::local_time())
{
}
~TestTimer()
{
using namespace std;
using namespace boost;
posix_time::ptime now(date_time::microsec_clock<posix_time::ptime>::local_time());
posix_time::time_duration d = now - start;
cout << name << " completed in " << d.total_milliseconds() / 1000.0 <<
" seconds" << endl;
}
private:
std::string name;
boost::posix_time::ptime start;
};
struct Pixel
{
Pixel()
{
}
Pixel(unsigned char r, unsigned char g, unsigned char b) : r(r), g(g), b(b)
{
}
unsigned char r, g, b;
};
void UseVector()
{
TestTimer t("UseVector");
for(int i = 0; i < 1000; ++i)
{
int dimension = 999;
std::vector<Pixel> pixels;
pixels.resize(dimension * dimension);
for(int i = 0; i < dimension * dimension; ++i)
{
pixels[i].r = 255;
pixels[i].g = 0;
pixels[i].b = 0;
}
}
}
void UseVectorPushBack()
{
TestTimer t("UseVectorPushBack");
for(int i = 0; i < 1000; ++i)
{
int dimension = 999;
std::vector<Pixel> pixels;
pixels.reserve(dimension * dimension);
for(int i = 0; i < dimension * dimension; ++i)
pixels.push_back(Pixel(255, 0, 0));
}
}
void UseArray()
{
TestTimer t("UseArray");
for(int i = 0; i < 1000; ++i)
{
int dimension = 999;
Pixel * pixels = (Pixel *)malloc(sizeof(Pixel) * dimension * dimension);
for(int i = 0 ; i < dimension * dimension; ++i)
{
pixels[i].r = 255;
pixels[i].g = 0;
pixels[i].b = 0;
}
free(pixels);
}
}
int main()
{
TestTimer t1("The whole thing");
UseArray();
UseVector();
UseVectorPushBack();
return 0;
}
Mache ich es falsch oder so? Oder habe ich gerade diesen Performance-Mythos gesprengt?
Ich verwende den Release-Modus in Visual Studio 2005 .
In Visual C ++ , #define _SECURE_SCL 0
verringert UseVector
um die Hälfte (bringt es auf 4 Sekunden nach unten). Das ist wirklich riesig, IMO.
vector
es sich um ein Allzweck-Array mit veränderbarer Größe handelt. Herzliche Glückwünsche. Wie bei allen Allzweckwerkzeugen ist es möglich, spezielle Situationen zu entwickeln, in denen sie nicht optimal sind. Aus diesem Grund besteht die konventionelle Weisheit darin, mit a zu beginnenvector
und gegebenenfalls Alternativen in Betracht zu ziehen.Antworten:
Verwenden Sie Folgendes:
Das Array ist also doppelt so schnell wie der Vektor.
Aber nach genauer Blick auf den Code dies wird erwartet; wenn Sie zweimal über den Vektor und das Array nur einmal laufen. Hinweis: Wenn Sie
resize()
den Vektor verwenden, weisen Sie nicht nur den Speicher zu, sondern durchlaufen auch den Vektor und rufen den Konstruktor für jedes Element auf.Ordnen Sie den Code leicht neu an, sodass der Vektor jedes Objekt nur einmal initialisiert:
Jetzt mache ich wieder das gleiche Timing:
Die Leistung des Vektors ist jetzt nur geringfügig schlechter als die des Arrays. IMO ist dieser Unterschied unbedeutend und kann durch eine ganze Reihe von Dingen verursacht werden, die nicht mit dem Test verbunden sind.
Ich würde auch berücksichtigen, dass Sie das Pixel-Objekt in der
UseArrray()
Methode nicht korrekt initialisieren / zerstören , da weder Konstruktor noch Destruktor aufgerufen werden (dies ist möglicherweise kein Problem für diese einfache Klasse, aber etwas etwas komplexeres (dh mit Zeigern oder Elementen) mit Zeigern) wird Probleme verursachen.quelle
reserve()
statt verwendenresize()
. Dadurch wird Platz für die Objekte zugewiesen (dh die Kapazität des Vektors wird geändert ), die Objekte werden jedoch nicht erstellt (dh die Größe des Vektors bleibt unverändert).resize()
mitreserve()
, denn dies ist nicht der Vektor der internen Vorstellung von seiner eigenen Größe nicht anpassen, so dass nachfolgende Schreibvorgänge auf ihre Elemente technisch sind „über das Ende zu schreiben“ und UB produzieren. Obwohl sich in der Praxis jede STL-Implementierung in dieser Hinsicht "selbst verhält", wie synchronisieren Sie die Größe des Vektors neu? Wenn Sie versuchen,resize()
nach dem Auffüllen des Vektors aufzurufen , werden möglicherweise alle diese Elemente mit standardmäßig erstellten Werten überschrieben!reserve
nur die Kapazität eines Vektors ändert, nicht seine Größe./EHsc
zu Kompilierungsschaltern hat das bereinigt undassign()
schlägt jetzt das Array. Yay.Gute Frage. Ich kam hierher und erwartete, eine einfache Lösung zu finden, die die Vektortests beschleunigen würde. Das hat nicht ganz so geklappt, wie ich erwartet hatte!
Optimierung hilft, reicht aber nicht aus. Bei aktivierter Optimierung sehe ich immer noch einen 2-fachen Leistungsunterschied zwischen UseArray und UseVector. Interessanterweise war UseVector ohne Optimierung deutlich langsamer als UseVectorPushBack.
Idee 1 - Verwenden Sie new [] anstelle von malloc
Ich habe versucht
malloc()
,new[]
in UseArray zu wechseln , damit die Objekte erstellt werden. Und von der Zuweisung einzelner Felder zur Zuweisung einer Pixelinstanz. Oh, und die innere Schleifenvariable umbenennen inj
.Überraschenderweise (für mich) machte keine dieser Änderungen einen Unterschied. Nicht einmal die Änderung, bei
new[]
der standardmäßig alle Pixel erstellt werden. Es scheint, dass gcc die Standardkonstruktoraufrufe bei Verwendung optimieren kannnew[]
, aber nicht bei Verwendungvector
.Idee 2 - Entfernen Sie wiederholte Operator [] -Aufrufe
Ich habe auch versucht, die dreifache
operator[]
Suche loszuwerden und den Verweis auf zwischenzuspeichernpixels[j]
. Das hat UseVector tatsächlich verlangsamt! Hoppla.Idee 3 - Konstruktoren entfernen
Was ist mit dem vollständigen Entfernen der Konstruktoren? Dann kann gcc vielleicht die Konstruktion aller Objekte optimieren, wenn die Vektoren erstellt werden. Was passiert, wenn wir Pixel ändern in:
Ergebnis: ca. 10% schneller. Immer noch langsamer als ein Array. Hm.
Idee 4 - Verwenden Sie den Iterator anstelle des Schleifenindex
Wie wäre es mit einem
vector<Pixel>::iterator
anstelle eines Schleifenindex?Ergebnis:
Nein, nicht anders. Zumindest ist es nicht langsamer. Ich dachte, dies hätte eine ähnliche Leistung wie # 2, wo ich eine
Pixel&
Referenz verwendet habe.Fazit
Selbst wenn einige intelligente Cookies herausfinden, wie die Vektorschleife so schnell wie die Array-Schleife gemacht werden kann, spricht dies nicht gut für das Standardverhalten von
std::vector
. So viel dazu, dass der Compiler intelligent genug ist, um die gesamte C ++ - Qualität zu optimieren und STL-Container so schnell wie Raw-Arrays zu machen.Unter dem Strich kann der Compiler die No-Op-Standardkonstruktoraufrufe bei der Verwendung nicht optimieren
std::vector
. Wenn Sie Plain verwenden, werdennew[]
diese optimiert. Aber nicht mitstd::vector
. Selbst wenn Sie Ihren Code neu schreiben können, um die Konstruktoraufrufe zu eliminieren, die angesichts des Mantras hier herumfliegen: "Der Compiler ist schlauer als Sie. Die STL ist genauso schnell wie normal C. Machen Sie sich darüber keine Sorgen."quelle
vector<int>
.Dies ist eine alte, aber beliebte Frage.
Zu diesem Zeitpunkt werden viele Programmierer in C ++ 11 arbeiten. Und in C ++ 11 läuft der geschriebene OP-Code für
UseArray
oder genauso schnellUseVector
.Das grundlegende Problem bestand darin, dass Ihre
Pixel
Struktur zwar nicht initialisiert wurde, jedochstd::vector<T>::resize( size_t, T const&=T() )
eine Standardkonstruktion verwendetPixel
und diese kopiert . Der Compiler bemerkte nicht, dass er aufgefordert wurde, nicht initialisierte Daten zu kopieren, und führte die Kopie tatsächlich durch.Hat in C ++ 11
std::vector<T>::resize
zwei Überladungen. Das erste iststd::vector<T>::resize(size_t)
, das andere iststd::vector<T>::resize(size_t, T const&)
. Das heißt, wenn Sieresize
ohne ein zweites Argument aufrufen , werden einfach Standardkonstrukte erstellt, und der Compiler ist intelligent genug, um zu erkennen, dass die Standardkonstruktion nichts bewirkt, sodass der Durchlauf über den Puffer übersprungen wird.(Die beiden Überladungen wurden hinzugefügt, um bewegliche, konstruierbare und nicht kopierbare Typen zu verarbeiten. Die Leistungsverbesserung bei der Arbeit an nicht initialisierten Daten ist ein Bonus.)
Die
push_back
Lösung führt auch eine Zaunpfostenprüfung durch, wodurch sie verlangsamt wird, sodass sie langsamer als diemalloc
Version bleibt .Live-Beispiel (ich habe auch den Timer durch ersetzt
chrono::high_resolution_clock
).Beachten Sie, dass Sie dies mit einem benutzerdefinierten
std::vector
Allokator tun können, wenn Sie eine Struktur haben, für die normalerweise eine Initialisierung erforderlich ist, die Sie jedoch nach dem Erweitern Ihres Puffers verarbeiten möchten . Wenn Sie es dann in einen normalerenstd::vector
Zustand versetzen möchten , glaube ich, dass eine sorgfältige Verwendungallocator_traits
und Überschreibung dies==
möglicherweise bewirken könnte, bin mir aber nicht sicher.quelle
emplace_back
es gegenpush_back
hier geht.clang++ -std=c++11 -O3
hatUseArray completed in 2.02e-07 seconds
undUseVector completed in 1.3026 seconds
. Ich habe auch eineUseVectorEmplaceBack
Version hinzugefügt, die ca. ist. 2,5x so schnell wieUseVectorPushBack
.Um fair zu sein, können Sie eine C ++ - Implementierung nicht mit einer C-Implementierung vergleichen, wie ich Ihre Malloc-Version nennen würde. malloc erstellt keine Objekte - es weist nur Rohspeicher zu. Dass Sie diesen Speicher dann als Objekte behandeln, ohne den Konstruktor aufzurufen, ist schlechtes C ++ (möglicherweise ungültig - das überlasse ich den Sprachanwälten).
Das heißt, das einfache Ändern des Mallocs in
new Pixel[dimensions*dimensions]
und das freie Ändern indelete [] pixels
macht keinen großen Unterschied bei der einfachen Implementierung von Pixel, die Sie haben. Hier sind die Ergebnisse auf meiner Box (E6600, 64-Bit):Aber mit einer kleinen Änderung dreht sich der Spieß um:
Pixel.h
Pixel.cc
main.cc
So zusammengestellt:
Wir erhalten sehr unterschiedliche Ergebnisse:
Mit einem nicht inlinierten Konstruktor für Pixel schlägt std :: vector jetzt ein Raw-Array.
Es scheint, dass die Komplexität der Zuordnung durch std :: vector und std: allocator zu groß ist, um so effektiv wie einfach optimiert zu werden
new Pixel[n]
. Wir können jedoch sehen, dass das Problem einfach in der Zuordnung und nicht im Vektorzugriff liegt, indem wir einige der Testfunktionen optimieren, um den Vektor / das Array einmal zu erstellen, indem wir ihn außerhalb der Schleife verschieben:und
Wir erhalten jetzt diese Ergebnisse:
Daraus können wir lernen, dass std :: vector mit einem Raw-Array für den Zugriff vergleichbar ist. Wenn Sie den Vektor / das Array jedoch mehrmals erstellen und löschen müssen, ist das Erstellen eines komplexen Objekts zeitaufwändiger als das Erstellen eines einfachen Arrays wenn der Konstruktor des Elements nicht inline ist. Ich finde das nicht sehr überraschend.
quelle
Versuchen Sie es damit:
Ich bekomme fast genau die gleiche Leistung wie mit Array.
Die Sache
vector
ist, dass es ein viel allgemeineres Werkzeug als ein Array ist. Und das bedeutet, dass Sie überlegen müssen, wie Sie es verwenden. Es kann auf viele verschiedene Arten verwendet werden und bietet Funktionen, die ein Array nicht einmal hat. Und wenn Sie es für Ihren Zweck "falsch" verwenden, entsteht viel Overhead. Wenn Sie es jedoch richtig verwenden, handelt es sich normalerweise um eine Datenstruktur ohne Overhead. In diesem Fall besteht das Problem darin, dass Sie den Vektor separat initialisiert haben (wodurch alle Elemente ihren Standard-Ctor aufgerufen haben) und dann jedes Element einzeln mit dem richtigen Wert überschreiben. Das ist für den Compiler viel schwieriger zu optimieren, als wenn Sie dasselbe mit einem Array tun. Aus diesem Grund bietet der Vektor einen Konstruktor, mit dem Sie genau das tun können: .N
X
Und wenn Sie das verwenden, ist der Vektor genauso schnell wie ein Array.
Also nein, Sie haben den Performance-Mythos nicht gesprengt. Aber Sie haben gezeigt, dass es nur wahr ist, wenn Sie den Vektor optimal nutzen, was auch ein ziemlich guter Punkt ist. :) :)
Auf der positiven Seite ist es wirklich die einfachste Verwendung, die sich als die schnellste herausstellt. Wenn Sie mein Code-Snippet (eine einzelne Zeile) mit John Kugelmans Antwort vergleichen, die jede Menge Optimierungen und Optimierungen enthält, die den Leistungsunterschied immer noch nicht ganz beseitigen, ist es ziemlich klar, dass
vector
es doch ziemlich clever gestaltet ist. Sie müssen nicht durch Reifen springen, um eine Geschwindigkeit zu erreichen, die einem Array entspricht. Im Gegenteil, Sie müssen die einfachste Lösung verwenden.quelle
new[]
führt dieselben Standardkonstruktionen ausvector.resize()
wie die, ist jedoch viel schneller.new[]
+ innere Schleife sollte die gleiche Geschwindigkeit haben wievector.resize()
+ innere Schleife, aber es ist nicht so, es ist fast doppelt so schnell.malloc
dem nichts initialisiert oder erstellt wird. Es handelt sich also genau wie in meinemvector
Beispiel um einen Single-Pass-Algorithmus . Und wasnew[]
die Antwort ist offensichtlich , dass beide erfordern zwei Durchgänge, aber imnew[]
Fall der Compiler in der Lage , zu optimieren , dass zusätzlicher Aufwand entfernt, die sie in der nicht tutvector
Fall. Aber ich verstehe nicht, warum es interessant ist, was in suboptimalen Fällen passiert. Wenn Sie Wert auf Leistung legen, schreiben Sie keinen solchen Code.vector::resize()
hinwegblitzen wollte, ist Array wohl wieder die optimale Lösung - da ich nicht sagen kann , dass ich einen kontingenten Speicherblock erhalten soll, ohne Zeit damit zu verschwenden, nutzlose Konstruktoren aufzurufen.malloc
das keine Initialisierung durchführt, aber in C ++ mit Nicht-POD-Typen nicht funktioniert. Im allgemeinen Fall wäre ein C ++ - Array genauso schlecht. Vielleicht ist die Frage, ob Sie, wenn Sie dieses Blitting häufig durchführen, nicht dasselbe Array / denselben Vektor wiederverwenden würden? Und wenn Sie das tun, zahlen Sie die Kosten für "nutzlose Konstrukteure" nur einmal zu Beginn. Das eigentliche Blitting geht doch genauso schnell.Es war kaum ein fairer Vergleich, als ich Ihren Code zum ersten Mal betrachtete. Ich dachte definitiv, Sie würden Äpfel nicht mit Äpfeln vergleichen. Also dachte ich, lassen Sie uns Konstruktoren und Destruktoren bei allen Tests aufrufen. und dann vergleichen.
Meine Gedanken waren, dass sie mit diesem Setup genau gleich sein sollten. Es stellt sich heraus, ich habe mich geirrt.
Warum trat dieser Leistungsverlust von 30% überhaupt auf? Die STL enthält alles in den Headern, daher sollte es dem Compiler möglich gewesen sein, alles zu verstehen, was erforderlich war.
Meine Gedanken waren, dass die Schleife alle Werte für den Standardkonstruktor initialisiert. Also habe ich einen Test durchgeführt:
Die Ergebnisse waren wie ich vermutet hatte:
Dies ist eindeutig die Ursache für die Verlangsamung, die Tatsache, dass der Vektor den Kopierkonstruktor verwendet, um die Elemente eines standardmäßig erstellten Objekts zu initialisieren.
Dies bedeutet, dass während der Konstruktion des Vektors die folgende Pseudooperationsreihenfolge auftritt:
Was aufgrund des vom Compiler erstellten impliziten Kopierkonstruktors auf Folgendes erweitert wird:
So Standard
Pixel
bleibt un-initialisiert, während der Rest initialisiert wird mit dem StandardPixel
‚s un initialisiert Werte.Im Vergleich zur alternativen Situation mit
New[]
/Delete[]
:Sie werden alle ihren nicht initialisierten Werten überlassen und ohne die doppelte Iteration über die Sequenz.
Wie können wir diese Informationen testen? Versuchen wir, den impliziten Kopierkonstruktor zu überschreiben.
Und die Ergebnisse?
Wenn Sie also sehr oft Hunderte von Vektoren erstellen, überdenken Sie Ihren Algorithmus .
In jedem Fall ist die STL- Implementierung aus einem unbekannten Grund nicht langsamer, sondern macht genau das, was Sie verlangen. Ich hoffe du weißt es besser.
quelle
Versuchen Sie, aktivierte Iteratoren zu deaktivieren und im Release-Modus zu erstellen. Sie sollten keinen großen Leistungsunterschied feststellen.
quelle
#define _SECURE_SCL 0
. Das hatUseVector
ungefähr 4 Sekunden gedauert (ähnlich wiegcc
unten), aber es ist immer noch doppelt so langsam.-O3
._HAS_ITERATOR_DEBUGGING
es im Release-Build deaktiviert ist: msdn.microsoft.com/en-us/library/aa985939(VS.80).aspxGNU STL (und andere), gegeben
vector<T>(n)
, default konstruieren ein prototypische ObjektT()
- der Compiler den leeren Konstruktor optimiert weg - aber dann eine Kopie jegliche Mülls passiert ist in den Speicheradressen nun für das Objekt reserviert sein wird von der STL genommen__uninitialized_fill_n_aux
, die Schleifen, die Kopien dieses Objekts als Standardwerte im Vektor füllen. "Meine" STL ist also kein Schleifenkonstruieren, sondern Konstruieren und dann Schleifen / Kopieren. Es ist nicht intuitiv, aber ich hätte mich daran erinnern müssen, als ich eine aktuelle Frage zum Stapelüberlauf zu diesem Punkt kommentierte: Das Konstrukt / die Kopie kann für referenzgezählte Objekte usw. effizienter sein.So:
oder
ist - bei vielen STL-Implementierungen - so etwas wie:
Das Problem ist, dass die aktuelle Generation von Compiler-Optimierern nicht aus der Erkenntnis heraus zu funktionieren scheint, dass Temp nicht initialisierter Müll ist, und die Schleifen- und Standardaufrufe des Kopierkonstruktors nicht optimieren kann. Sie könnten glaubwürdig argumentieren, dass Compiler dies auf keinen Fall optimieren sollten, da ein Programmierer, der das Obige schreibt, eine vernünftige Erwartung hat, dass alle Objekte nach der Schleife identisch sind, selbst wenn es sich um Müll handelt (übliche Einschränkungen bezüglich 'identisch' / operator == vs. memcmp / operator = etc gelten). Es ist nicht zu erwarten, dass der Compiler einen zusätzlichen Einblick in den größeren Kontext von std :: vector <> oder die spätere Verwendung der Daten erhält, die diese Optimierung für sicher halten würden.
Dies kann der offensichtlicheren, direkteren Implementierung gegenübergestellt werden:
Was wir von einem Compiler erwarten können, um zu optimieren.
Beachten Sie Folgendes, um die Rechtfertigung für diesen Aspekt des Verhaltens des Vektors etwas genauer zu erläutern:
Es ist eindeutig ein großer Unterschied, ob wir 10000 unabhängige Objekte gegenüber 10000 Objekten erstellen, die auf dieselben Daten verweisen. Es gibt ein vernünftiges Argument dafür, dass der Vorteil des Schutzes gelegentlicher C ++ - Benutzer vor versehentlichem Handeln etwas so Teueres die sehr geringen realen Kosten einer schwer zu optimierenden Kopierkonstruktion überwiegt.
ORIGINAL ANTWORT (als Referenz / Sinn für die Kommentare): Keine Chance. Der Vektor ist so schnell wie ein Array, zumindest wenn Sie sinnvoll Platz reservieren. ...
quelle
Die Antwort von Martin York stört mich, weil es wie ein Versuch erscheint, das Initialisierungsproblem unter den Teppich zu streichen. Zu Recht identifiziert er jedoch redundante Standardkonstruktionen als Ursache für Leistungsprobleme.
[EDIT: Martins Antwort schlägt nicht mehr vor, den Standardkonstruktor zu ändern.]
Für das unmittelbare Problem könnten Sie sicherlich
vector<Pixel>
stattdessen die 2-Parameter-Version des ctor aufrufen :Dies funktioniert, wenn Sie mit einem konstanten Wert initialisieren möchten, was häufig der Fall ist. Das allgemeinere Problem ist jedoch: Wie können Sie effizient mit etwas Komplizierterem als einem konstanten Wert initialisieren?
Hierfür können Sie
back_insert_iterator
einen Iteratoradapter verwenden. Hier ist ein Beispiel mit einem Vektor vonint
s, obwohl die allgemeine Idee fürPixel
s genauso gut funktioniert :Alternativ können Sie
copy()
odertransform()
anstelle von verwendengenerate_n()
.Der Nachteil ist, dass die Logik zum Erstellen der Anfangswerte in eine separate Klasse verschoben werden muss, was weniger praktisch ist als das Einrichten (obwohl Lambdas in C ++ 1x dies viel schöner machen). Ich gehe auch davon aus, dass dies immer noch nicht so schnell sein wird wie eine
malloc()
Nicht-STL-basierte Version, aber ich gehe davon aus, dass es eng wird, da nur eine Konstruktion für jedes Element ausgeführt wird.quelle
Die Vektoraufrufe rufen zusätzlich Pixelkonstruktoren auf.
Jedes verursacht fast eine Million ctor-Läufe, die Sie planen.
edit: dann gibt es die äußere 1 ... 1000 Schleife, also mach, dass eine Milliarde ctor anruft!
Bearbeiten 2: Es wäre interessant, die Demontage für den UseArray-Fall zu sehen. Ein Optimierer könnte das Ganze weg optimieren, da es keine andere Wirkung hat als das Brennen der CPU.
quelle
So funktioniert die
push_back
Methode in Vektor:Nach dem Aufruf von
push_back
X Elementen:Wiederholen. Wenn du nicht bist
reserving
Weltraum sind, wird es definitiv langsamer. Mehr noch, wenn es teuer ist, den Gegenstand zu kopieren, dann wird dich 'push_back' so lebendig essen.In
vector
Bezug auf die Sache mit dem Array muss ich den anderen Leuten zustimmen. Führen Sie das Release aus, aktivieren Sie die Optimierungen und setzen Sie ein paar weitere Flags ein, damit die freundlichen Mitarbeiter von Microsoft es nicht für Sie tun.Wenn Sie die Größe nicht ändern müssen, verwenden Sie Boost.Array.
quelle
reserve
wie ich sollte.push_back
hat konstante Zeit amortisiert. Es hört sich so an, als würden Sie einen O (N) -Prozess beschreiben. (Die Schritte 1 und 3 scheinen völlig fehl am Platz zu sein.) Waspush_back
OP langsam macht, ist die Bereichsprüfung, um festzustellen, ob eine Neuzuweisung erforderlich ist, die Aktualisierung der Zeiger, die Überprüfung gegen NULL innerhalb der Platzierungnew
und andere kleine Dinge, die normalerweise übertönt werden die eigentliche Arbeit des Programms.reserve
wenn es immer noch diese Überprüfung (ob es neu zugewiesen werden muss) bei jedem durchführen musspush_back
.vector
Benutzer seine Größenänderungsfunktion ausführt, es ist nur "Magie". Lassen Sie mich das hier etwas näher erläutern.Einige Profilerdaten (Pixel sind auf 32 Bit ausgerichtet):
Blah
In
allocator
:vector
::Array
Der größte Teil des Overheads entfällt auf den Kopierkonstruktor. Beispielsweise,
Es hat die gleiche Leistung wie ein Array.
quelle
pixels.size()
gebrochen.Mein Laptop ist Lenova G770 (4 GB RAM).
Das Betriebssystem ist Windows 7 64-Bit (das mit Laptop)
Compiler ist MinGW 4.6.1.
Die IDE lautet Code :: Blocks .
Ich teste die Quellcodes des ersten Beitrags.
Die Ergebnisse
O2-Optimierung
UseArray in 2.841 Sekunden abgeschlossen
UseVector in 2,548 Sekunden abgeschlossen
UseVectorPushBack wurde in 11,95 Sekunden abgeschlossen
Das Ganze war in 17.342 Sekunden erledigt
Systempause
O3-Optimierung
UseArray in 1.452 Sekunden abgeschlossen
UseVector in 2,514 Sekunden abgeschlossen
UseVectorPushBack wurde in 12.967 Sekunden abgeschlossen
Das Ganze war in 16.937 Sekunden erledigt
Es sieht so aus, als ob die Leistung des Vektors unter O3-Optimierung schlechter ist.
Wenn Sie die Schleife auf ändern
Die Geschwindigkeit von Array und Vektor unter O2 und O3 ist nahezu gleich.
quelle
Als besserer Benchmark (glaube ich ...) kann der Compiler aufgrund von Optimierungen den Code ändern, da die Ergebnisse der zugewiesenen Vektoren / Arrays nirgendwo verwendet werden. Ergebnisse:
Compiler:
ZENTRALPROZESSOR:
Und der Code:
quelle
Ich habe einige umfangreiche Tests durchgeführt, die ich jetzt schon eine Weile machen wollte. Könnte dies auch teilen.
Dies ist meine Dual-Boot-Maschine i7-3770, 16 GB RAM, x86_64, unter Windows 8.1 und unter Ubuntu 16.04. Weitere Informationen und Schlussfolgerungen finden Sie weiter unten. Getestet sowohl MSVS 2017 als auch g ++ (sowohl unter Windows als auch unter Linux).
Testprogramm
Ergebnisse
Anmerkungen
std::sort()
(Sie können sehen, dass dies auskommentiert ist), sie aber später entfernt, da es keine signifikanten relativen Unterschiede gab.Meine Schlussfolgerungen und Bemerkungen
std::array
Zeitschwankungen zwischen aufeinanderfolgenden Läufen fest, während andere, insbesondere die Standardstrukturen von std :: data, im Vergleich stark variiertenstd::array
Arrays im C-Stil unter Windows ohne Optimierung schnellerUrteil
Dies ist natürlich Code für einen optimierten Build. Und da ging es um die Frage
std::vector
dann war, ist es! Viel! langsamer als normale Arrays (optimiert / nicht optimiert). Wenn Sie jedoch einen Benchmark durchführen, möchten Sie natürlich optimierten Code erstellen.Der Star der Show war für mich jedoch
std::array
.quelle
Mit den richtigen Optionen können Vektoren und Arrays identische Asm erzeugen . In diesen Fällen haben sie natürlich die gleiche Geschwindigkeit, da Sie in beiden Fällen dieselbe ausführbare Datei erhalten.
quelle
Übrigens tritt die Verlangsamung Ihres Sehens in Klassen mit Vektor auch bei Standardtypen wie int auf. Hier ist ein Multithread-Code:
Das Verhalten aus dem Code zeigt, dass die Instanziierung des Vektors der längste Teil des Codes ist. Sobald Sie durch diesen Flaschenhals kommen. Der Rest des Codes läuft extrem schnell. Dies gilt unabhängig davon, auf wie vielen Threads Sie ausgeführt werden.
Ignorieren Sie übrigens die absolut verrückte Anzahl von Includes. Ich habe diesen Code verwendet, um Dinge für ein Projekt zu testen, damit die Anzahl der Includes weiter wächst.
quelle
Ich möchte nur erwähnen, dass vector (und smart_ptr) nur eine dünne Schicht ist, die über Raw-Arrays (und Raw-Zeigern) hinzugefügt wird. Und tatsächlich ist die Zugriffszeit eines Vektors im kontinuierlichen Speicher schneller als die eines Arrays. Der folgende Code zeigt das Ergebnis der Initialisierung und des Zugriffs auf Vektor und Array.
Die Ausgabe ist:
Die Geschwindigkeit ist also fast gleich, wenn Sie sie richtig verwenden. (wie andere mit Reserve () oder Resize () erwähnt).
quelle
Nun, weil vector :: resize () viel mehr verarbeitet als die einfache Speicherzuweisung (von malloc).
Versuchen Sie, einen Haltepunkt in Ihren Kopierkonstruktor einzufügen (definieren Sie ihn so, dass Sie einen Haltepunkt setzen können!), Und es entsteht zusätzliche Verarbeitungszeit.
quelle
Ich muss sagen, dass ich kein Experte für C ++ bin. Aber um einige Versuchsergebnisse hinzuzufügen:
kompilieren: gcc-6.2.0 / bin / g ++ -O3 -std = c ++ 14 vector.cpp
Maschine:
Betriebssystem:
Ausgabe:
Hier ist das einzige, was ich seltsam finde, die Leistung von "UseFillConstructor" im Vergleich zu "UseConstructor".
Der Code:
Der zusätzliche "Wert" verlangsamt die Leistung erheblich, was meiner Meinung nach auf den mehrfachen Aufruf des Kopierkonstruktors zurückzuführen ist. Aber...
Kompilieren:
Ausgabe:
In diesem Fall ist die gcc-Optimierung sehr wichtig, kann Ihnen jedoch nicht viel helfen, wenn standardmäßig ein Wert angegeben wird. Das ist eigentlich gegen meinen Unterricht. Hoffentlich hilft es neuen Programmierern bei der Auswahl des Vektorinitialisierungsformats.
quelle
Es scheint von den Compiler-Flags abzuhängen. Hier ist ein Benchmark-Code:
Unterschiedliche Optimierungsflags geben unterschiedliche Antworten:
Ihre genauen Ergebnisse variieren, aber dies ist auf meinem Computer recht typisch.
quelle
Nach meiner Erfahrung kann manchmal, nur manchmal
vector<int>
um ein Vielfaches langsamer sein alsint[]
. Eine Sache zu beachten ist, dass Vektoren von Vektoren sehr unterschiedlich sindint[][]
. Da die Elemente im Speicher wahrscheinlich nicht zusammenhängend sind. Dies bedeutet, dass Sie die Größe verschiedener Vektoren innerhalb des Hauptvektors ändern können, die CPU jedoch möglicherweise nicht so gut Elemente zwischenspeichern kann wie im Fall vonint[][]
.quelle