it-swarm.com.de

Was ist schneller, einen STL-Vektor mit vector :: iterator oder mit at () zu iterieren?

Was würde in Bezug auf die Leistung schneller funktionieren? Ist da ein Unterschied? Ist es plattformabhängig? 

//1. Using vector<string>::iterator:
vector<string> vs = GetVector();

for(vector<string>::iterator it = vs.begin(); it != vs.end(); ++it)
{
   *it = "Am I faster?";
}

//2. Using size_t index:
for(size_t i = 0; i < vs.size(); ++i)
{
   //One option:
   vs.at(i) = "Am I faster?";
   //Another option:
   vs[i] = "Am I faster?";
}
53
Gal Goldman

Warum nicht einen Test schreiben und herausfinden? 

Edit: Mein schlechtes - Ich dachte, ich habe das Timing der optimierten Version gemacht, war es aber nicht. Auf meinem Rechner, der mit g ++ -O2 kompiliert wurde, ist die Iteratorversion etwas langsamer als die Operator [] -Version, aber wahrscheinlich nicht signifikant.

#include <vector>
#include <iostream>
#include <ctime>
using namespace std;

int main() {
    const int BIG = 20000000;
    vector <int> v;
    for ( int i = 0; i < BIG; i++ ) {
        v.Push_back( i );
    }

    int now = time(0);
    cout << "start" << endl;
    int n = 0;
    for(vector<int>::iterator it = v.begin(); it != v.end(); ++it) {
        n += *it;
    }

    cout << time(0) - now << endl;
    now = time(0);
    for(size_t i = 0; i < v.size(); ++i) {
        n += v[i];
    }
    cout << time(0) - now << endl;

    return n != 0;
}
26
anon

Die Verwendung eines Iterators führt zum Inkrementieren eines Zeigers (zum Inkrementieren) und zum Dereferenzieren in Dereferenzieren eines Zeigers.
Bei einem Index sollte das Inkrementieren gleich schnell sein, aber das Auffinden eines Elements erfordert eine Addition (Datenzeiger + Index) und eine Dereferenzierung dieses Zeigers, der Unterschied sollte jedoch gering sein.
at() prüft auch, ob der Index innerhalb der Grenzen liegt, sodass er möglicherweise langsamer ist.

Benchmark-Ergebnisse für 500M-Iterationen, Vektorgröße 10, mit gcc 4.3.3 (-O3), Linux 2.6.29.1 x86_64:
at(): 9158ms
operator[]: 4269ms
iterator: 3914ms

YMMV, aber wenn die Verwendung eines Index den Code lesbarer/verständlicher macht, sollten Sie dies tun.

35
tstenner

Wenn Sie keine Indizierung benötigen, verwenden Sie sie nicht. Das Iterator-Konzept ist für Sie da. Iteratoren sind sehr einfach zu optimieren, während für den direkten Zugriff zusätzliche Kenntnisse erforderlich sind.

Die Indizierung ist für den direkten Zugriff gedacht. Die Klammern und die at-Methode tun dies. Im Gegensatz zu [] prüft at die Indizierung außerhalb des zulässigen Bereichs und ist daher langsamer.

Das Credo lautet: Fragen Sie nicht nach dem, was Sie nicht brauchen. Dann berechnet Ihnen der Compiler nicht das, was Sie nicht verwenden.

15
xtofl

Da Sie die Effizienz in Betracht ziehen, sollten Sie feststellen, dass die folgenden Variationen potenziell effizienter sind:

//1. Using vector<string>::iterator:

vector<string> vs = GetVector();
for(vector<string>::iterator it = vs.begin(), end = vs.end(); it != end; ++it)
{
   //...
}

//2. Using size_t index:

vector<string> vs = GetVector();
for(size_t i = 0, size = vs.size(); i != size; ++i)
{
   //...
}

da die Funktion end/size nur einmal und nicht jedes Mal durch die Schleife aufgerufen wird. Es ist wahrscheinlich, dass der Compiler diese Funktionen sowieso integriert, aber auf diese Weise wird sichergestellt.

14
James Hopkin

Wie alle anderen hier sagen, Benchmarks.

Trotzdem würde ich behaupten, dass der Iterator schneller ist, da at () auch die Bereichsüberprüfung durchführt, d. H. Er löst eine out_of_range-Ausnahme aus, wenn der Index außerhalb der Grenzen liegt. Diese Prüfung selbst verursacht wahrscheinlich einen gewissen Aufwand.

5

Ich würde vermuten, dass die erste Variante schneller ist.

Aber es hängt von der Implementierung ab. Um sicherzugehen, sollten Sie Ihren eigenen Code erstellen.

Warum einen eigenen Code erstellen?

Weil diese Faktoren alle die Ergebnisse variieren:

  • Welches Betriebssystem?
  • Welcher Compiler?
  • Welche Implementierung von STL wurde verwendet
  • Wurden Optimierungen aktiviert?
  • ... (andere Faktoren)
4
Brian R. Bondy

Es hängt davon ab, ob.

Die Antwort ist viel subtiler als die vorhandenen Antworten zeigen.

at ist immer langsamer als Iteratoren oder operator[].
Aber für operator[] Vs. Iteratoren hängt es ab von:

  1. Wie genau Sie operator[] Verwenden.

  2. Gibt an, ob Ihre bestimmte CPU Indexregister hat (ESI/EDI Auf x86).

  3. Inwieweit anderer Code denselben Index verwendet, der an operator[] Übergeben wird.
    (z. B. Indizieren Sie im Gleichschritt durch mehrere Arrays?)

Hier ist der Grund:

  1. Wenn du so etwas machst

    std::vector<unsigned char> a, b;
    for (size_t i = 0; i < n; ++i)
    {
        a[13 * i] = b[37 * i];
    }
    

    Dann ist dieser Code wahrscheinlich viel langsamer als die Iterator-Version, da er bei jeder Iteration von eine Multiplikationsoperation ausführt die Schleife!

    Ebenso, wenn Sie etwas tun wie:

    struct T { unsigned char a[37]; };
    std::vector<T> a;
    for (size_t i = 0; i < n; ++i)
    {
        a[i] = foo(i);
    }
    

    Dann wird dies wahrscheinlich also langsamer sein als die Iteratorversion, da sizeof(T) keine Potenz von 2 ist , und deshalb multiplizieren Sie (erneut) mit 37 bei jeder Schleife!

  2. Wenn Ihre CPU Indexregister hat, kann Ihr Code genauso gut oder sogar besser mit Indizes als mit Iteratoren arbeiten , wenn die Verwendung des Indexregisters ein anderes Register freigibt zur Verwendung in der Schleife. Dies ist nicht etwas, das Sie nur durch einen Blick erkennen können; Sie müssten den Code profilieren und/oder zerlegen.

  3. Wenn sich mehrere Arrays den gleichen Index teilen können, muss der Code nur einen Index inkrementieren , anstatt mehrere Iteratoren zu inkrementieren, was das Schreiben in den Speicher und damit allgemein reduziert steigert die Leistung. Wenn Sie jedoch nur über ein einzelnes Array iterieren, ist ein Iterator möglicherweise schneller, da Sie keinen Offset zu einem vorhandenen Basiszeiger hinzufügen müssen.

Im Allgemeinen sollten Sie Iteratoren bevorzugen Indizes und Indizes Zeigern vorziehen, bis und sofern Sie nicht auf einen Engpass stoßen, der sich aus der Profilerstellung ergibt, ist ein Wechsel von Vorteil, da Iteratoren sind Allzweck-Iteratoren und wahrscheinlich bereits der schnellste Ansatz. Die Daten müssen nicht nach dem Zufallsprinzip adressierbar sein, sodass Sie bei Bedarf Container austauschen können. Indizes sind das nächste bevorzugte Werkzeug, da sie noch keinen direkten Zugriff auf die Daten erfordern - sie werden seltener ungültig, und Sie können z. Ersetzen Sie deque durch vector ohne Probleme. Zeiger sollten ein letzter Ausweg sein und sie werden sich nur dann als nützlich erweisen, wenn Iteratoren nicht bereits im Release-Modus zu Potinern degenerieren.

2
Mehrdad

Es hängt wirklich von dem ab, was Sie tun, aber wenn Sie den Iterator immer wieder neu deklarieren müssen, werden die Iteratoren MARGINAL langsamer. In meinen Tests besteht die schnellste mögliche Iteration darin, ein einfaches * für Ihr Vektorenarray zu deklarieren und dadurch zu iterieren.

zum Beispiel:

Vektor-Iteration und Ziehen von zwei Funktionen pro Durchlauf.

vector<MyTpe> avector(128);
vector<MyTpe>::iterator B=avector.begin();
vector<MyTpe>::iterator E=avector.end()-1;
for(int i=0; i<1024; ++i){
 B=avector.begin();
   while(B!=E)
   {
       float t=B->GetVal(Val1,12,Val2); float h=B->GetVal(Val1,12,Val2);
    ++B;
  }}

Vector dauerte 90 Klicks (0,090000 Sekunden)

Aber wenn Sie es mit Zeigern getan haben ...

for(int i=0; i<1024; ++i){
MyTpe *P=&(avector[0]);
   for(int i=0; i<avector.size(); ++i)
   {
   float t=P->GetVal(Val1,12,Val2); float h=P->GetVal(Val1,12,Val2);
   }}

Vektor dauerte 18 Klicks (0,018000 Sekunden)

Welches ist ungefähr gleichbedeutend mit ...

MyTpe Array[128];
for(int i=0; i<1024; ++i)
{
   for(int p=0; p<128; ++p){
    float t=Array[p].GetVal(Val1, 12, Val2); float h=Array[p].GetVal(Val2,12,Val2);
    }}

Array 15 Klicks (0,015000 Sekunden).

Wenn Sie den Aufruf von avector.size () aufheben, wird die Uhrzeit gleich. 

Schließlich mit [] anrufen

for(int i=0; i<1024; ++i){
   for(int i=0; i<avector.size(); ++i){
   float t=avector[i].GetVal(Val1,12,Val2); float h=avector[i].GetVal(Val1,12,Val2);
   }}

Vektor dauerte 33 Klicks (0,033000 Sekunden)

Zeitmessung mit Uhr ()

2
adammonroe

Der erste ist im Debug-Modus schneller, da der Indexzugriff Iteratoren hinter der Szene erzeugt. Im Release-Modus, in dem alles eingebettet sein sollte, sollte der Unterschied jedoch vernachlässigbar oder null sein

2
Zorglub

Sie können diesen Testcode verwenden und die Ergebnisse vergleichen! Dio it!

#include <vector> 
#include <iostream> 
#include <ctime> 
using namespace std;; 


struct AAA{
    int n;
    string str;
};
int main() { 
    const int BIG = 5000000; 
    vector <AAA> v; 
    for ( int i = 0; i < BIG; i++ ) { 
        AAA a = {i, "aaa"};
        v.Push_back( a ); 
    } 

    clock_t now;
    cout << "start" << endl; 
    int n = 0; 
    now = clock(); 
    for(vector<AAA>::iterator it = v.begin(); it != v.end(); ++it) { 
        n += it->n; 
    } 
   cout << clock() - now << endl; 

    n = 0;
    now = clock(); 
    for(size_t i = 0; i < v.size(); ++i) { 
        n += v[i].n; 
    } 
    cout << clock() - now << endl; 

    getchar();
    return n != 0; 
} 
2
Mostaaf

Ich habe diesen Thread jetzt gefunden, als ich versuchte, meinen OpenGL-Code zu optimieren, und wollte meine Ergebnisse teilen, obwohl der Thread alt ist.

Hintergrund: Ich habe 4 Vektoren mit Größen zwischen 6 und 12. Das Schreiben erfolgt nur einmal am Anfang des Codes und das Lesen erfolgt für jedes der Elemente in den Vektoren alle 0,1 Millisekunden

Das Folgende ist die abgespeckte Version des zuerst verwendeten Codes:

for(vector<T>::iterator it = someVector.begin(); it < someVector.end(); it++)
{
    T a = *it;

    // Various other operations
}

Bei dieser Methode betrug die Bildrate etwa 7 Bilder pro Sekunde (fps).

Als ich den Code jedoch wie folgt änderte, verdoppelte sich die Framerate fast auf 15fps.

for(size_t index = 0; index < someVector.size(); ++index)
{
    T a = someVector[index];

    // Various other operations
}
1
Karthik

Wenn Sie VisualStudio 2005 oder 2008 verwenden, müssen Sie den Wert _ SECURE_SCL = 0 definieren, um die beste Leistung aus dem Vektor herauszuholen

Standardmäßig ist _SECURE_SCL aktiviert, wodurch das Durchlaufen eines Containers wesentlich langsamer wird. Das heißt, lassen Sie es in Debug-Builds an, es macht das Auffinden von Fehlern viel einfacher. Ein Wort der Vorsicht, da das Makro die Größe der Iteratoren und Container ändert, müssen Sie für alle Kompilierungseinheiten, die einen stl-Container verwenden, konsistent sein.

1
Stephen Nutt

Ich denke, die einzige Antwort könnte ein Test auf Ihrer Plattform sein. Im Allgemeinen ist das einzige, was in der STL standardisiert wird, die Art der Iteratoren, die eine Sammlung bietet, und die Komplexität der Algorithmen.

Ich würde sagen, dass es keinen (keinen großen Unterschied) zwischen diesen beiden Versionen gibt - der einzige Unterschied, den ich mir vorstellen könnte, wäre, dass der Code die gesamte Auflistung durchlaufen muss, wenn er die Länge eines Arrays berechnen muss (ich Ich bin mir nicht sicher, ob die Länge in einer Variablen innerhalb des Vektors gespeichert ist.

Der Zugriff auf die Elemente mit "at" sollte etwas länger dauern als der direkte Zugriff auf [], da er prüft, ob Sie sich in den Grenzen des Vektors befinden, und eine Ausnahme auslöst, wenn Sie außerhalb der Grenzen sind (normalerweise scheint es [] Zeigerarithmetik verwenden - so sollte es schneller sein

1
bernhardrusch

Der Unterschied sollte vernachlässigbar sein. std :: vector garantiert, dass seine Elemente nacheinander im Speicher angeordnet werden. Daher implementieren die meisten stl-Implementierungen Iteratoren in std :: vector als einfachen Zeiger. Aus diesem Grund sollte der einzige Unterschied zwischen den beiden Versionen darin bestehen, dass die erste einen Zeiger inkrementiert und in der zweiten einen Index, der dann zu einem Zeiger hinzugefügt wird. Meine Vermutung wäre also die zweite, vielleicht eine extrem schnelle (zyklische) Maschinenanweisung mehr.

Testen Sie den Maschinencode, den Ihr Compiler erzeugt.

Im Allgemeinen wäre es jedoch ratsam, sich zu profilieren, wenn es wirklich wichtig ist. Wenn Sie über diese Art von Frage vorzeitig nachdenken, erhalten Sie normalerweise nicht zu viel zurück. In der Regel befinden sich die Hotspots Ihres Codes an anderen Stellen, an denen Sie auf den ersten Blick keinen Verdacht haben.

0
Tobias

Nur wenig tangential zur ursprünglichen Frage, aber die schnellste Schleife wäre 

for( size_t i=size() ; i-- ; ) { ... }

das würde natürlich nach unten zählen. Dies führt zu erheblichen Einsparungen, wenn Sie eine große Anzahl von Iterationen in Ihrer Schleife haben, die jedoch nur eine kleine Anzahl sehr schneller Operationen enthält. 

Mit dem Operator [] ist dies möglicherweise schneller als viele der bereits veröffentlichten Beispiele.

0
jam spandex

Ich habe hier einen Code geschrieben, der in Code :: Blocks v12.11 unter Verwendung des Standard-mingw-Compilers ..__ kompiliert wurde. Dieser erstellt einen riesigen Vektor und greift dann auf jedes Element mithilfe der Iteratoren at () und index ..__ wird einmal durchlaufen, indem das letzte Element nach Funktion aufgerufen wird, und einmal, indem das letzte Element im temporären Speicher gespeichert wird.

Das Timing erfolgt mit GetTickCount.

#include <iostream>
#include <windows.h>
#include <vector>
using namespace std;

int main()
{
    cout << "~~ Vector access speed test ~~" << endl << endl;
    cout << "~ Initialization ~" << endl;
    long long t;
    int a;
    vector <int> test (0);
    for (int i = 0; i < 100000000; i++)
    {
        test.Push_back(i);
    }
    cout << "~ Initialization complete ~" << endl << endl;


    cout << "     iterator test: ";
    t = GetTickCount();
    for (vector<int>::iterator it = test.begin(); it < test.end(); it++)
    {
        a = *it;
    }
    cout << GetTickCount() - t << endl;



    cout << "Optimised iterator: ";
    t=GetTickCount();
    vector<int>::iterator endofv = test.end();
    for (vector<int>::iterator it = test.begin(); it < endofv; it++)
    {
        a = *it;
    }
    cout << GetTickCount() - t << endl;



    cout << "                At: ";
    t=GetTickCount();
    for (int i = 0; i < test.size(); i++)
    {
        a = test.at(i);
    }
    cout << GetTickCount() - t << endl;



    cout << "      Optimised at: ";
    t = GetTickCount();
    int endof = test.size();
    for (int i = 0; i < endof; i++)
    {
        a = test.at(i);
    }
    cout << GetTickCount() - t << endl;



    cout << "             Index: ";
    t=GetTickCount();
    for (int i = 0; i < test.size(); i++)
    {
        a = test[i];
    }
    cout << GetTickCount() - t << endl;



    cout << "   Optimised Index: ";
    t = GetTickCount();
    int endofvec = test.size();
    for (int i = 0; i < endofvec; i++)
    {
        a = test[i];
    }
    cout << GetTickCount() - t << endl;

    cin.ignore();
}

Basierend darauf habe ich persönlich festgestellt, dass "optimierte" Versionen schneller sind als "nicht optimierte" Iteratoren sind langsamer als vector.at (), was langsamer ist als direkte Indizes.

Ich schlage vor, dass Sie den Code selbst kompilieren und ausführen.

EDIT: Dieser Code wurde zurückgeschrieben, als ich mit C/C++ weniger Erfahrung hatte. Ein weiterer Testfall sollte darin bestehen, Präfixinkrement-Operatoren anstelle von Postfix zu verwenden. Das sollte die Laufzeit besser machen.

0
ithenoob