web-dev-qa-db-fra.com

Quoi de plus rapide, itérer un vecteur STL avec vector :: iterator ou avec at ()?

En termes de performances, qu'est-ce qui fonctionnerait plus vite? Y a-t-il une différence? Est-ce que cela dépend de la plateforme? 

//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

Pourquoi ne pas écrire un test et découvrir?

Edit: Mon mauvais - je pensais chronométrer la version optimisée mais je ne l’étais pas. Sur ma machine, compilée avec g ++ -O2, la version de l’itérateur est légèrement plus lente que la version de l’opérateur [], mais probablement pas de manière significative.

#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

L'utilisation d'un itérateur entraîne l'incrémentation d'un pointeur (pour l'incrémentation) et le déréférencement dans le déréférencement d'un pointeur.
Avec un index, l’incrémentation devrait être aussi rapide, mais rechercher un élément implique une addition (pointeur de données + index) et le déréférencement de ce pointeur, mais la différence doit être marginale.
at() vérifie également si l'index est dans les limites, de sorte qu'il pourrait être plus lent.

Résultats de référence pour 500 millions d’itérations, taille du vecteur 10, avec gcc 4.3.3 (-O3), linux 2.6.29.1 x86_64:
at(): 9158ms
operator[]: 4269ms
iterator: 3914ms 

YMMV, mais si l’utilisation d’un index rend le code plus lisible/compréhensible, vous devriez le faire.

34
tstenner

Si vous n'avez pas besoin d'indexation, ne l'utilisez pas. Le concept d'itérateur est là pour votre mieux. Les itérateurs sont très faciles à optimiser, tandis que l'accès direct nécessite des connaissances supplémentaires.

L'indexation est destinée à un accès direct. Les crochets et la méthode at le font. at, contrairement à [], vérifiera l'indexation hors limites, elle sera donc plus lente.

Le credo est le suivant: ne demandez pas ce dont vous n’avez pas besoin. Ensuite, le compilateur ne vous facturera pas ce que vous n'utilisez pas.

15
xtofl

Puisque vous cherchez efficacité, vous devez savoir que les variantes suivantes sont potentiellement plus efficaces:

//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)
{
   //...
}

puisque la fonction end/size n’est appelée qu’une fois plutôt que chaque fois dans la boucle. Il est probable que le compilateur incorporera ces fonctions de toute façon, mais de cette façon, vous en êtes sûr.

14
James Hopkin

Comme tout le monde le dit ici, faites des repères.

Cela dit, je dirais que l’itérateur est plus rapide puisque at () vérifie également la plage, c’est-à-dire qu’il lève une exception out_of_range si l’index est hors limites. Ce contrôle lui-même engendre probablement des frais généraux.

5
Mats Fredriksson

Je suppose que la première variante est plus rapide.

Mais cela dépend de la mise en œuvre. Pour être sûr que vous devriez profiler votre propre code.

Pourquoi profiler votre propre code?

Parce que ces facteurs vont tous varier les résultats:

  • Quel OS
  • Quel compilateur
  • Quelle implémentation de STL était utilisée
  • Les optimisations ont-elles été activées?
  • ... (autres facteurs)
4
Brian R. Bondy

Cela dépend vraiment de ce que vous faites, mais si vous devez répéter la déclaration de l'itérateur, les itérateurs deviennent MARGINALEMENT PLUS LENTS. Dans mes tests, l'itération la plus rapide possible consisterait à déclarer un simple * dans votre tableau de vecteurs et à le parcourir.

par exemple:

Itération de vecteur et extraction de deux fonctions par passe.

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;
  }}

Vecteur a pris 90 clics (0,090000 secondes)

Mais si vous l'avez fait avec des pointeurs ...

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);
   }}

Le vecteur a pris 18 clics (0.018000 secondes)

Ce qui équivaut à peu près à ...

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);
    }}

Rangée A pris 15 clics (0,015000 secondes).

Si vous éliminez l'appel à avector.size (), l'heure devient la même. 

Enfin, appeler avec []

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);
   }}

Vecteur a pris 33 clics (0,033000 secondes)

Chronométré avec horloge ()

2
adammonroe

Le premier sera plus rapide en mode débogage car l’accès à l’index crée des itérateurs en arrière-plan, mais en mode release où tout devrait être en ligne, la différence doit être négligeable ou nulle

2
Zorglub

Vous pouvez utiliser ce code de test et comparer les résultats! 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

J'ai trouvé ce fil maintenant en essayant d'optimiser mon code OpenGL et je voulais partager mes résultats même si le fil est ancien.

Arrière-plan: / J'ai 4 vecteurs, des tailles allant de 6 à 12. L'écriture n'a lieu qu'une fois au début du code et la lecture a lieu pour chacun des éléments des vecteurs toutes les 0,1 millisecondes.

Voici la version simplifiée du code utilisé en premier:

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

    // Various other operations
}

Le taux de trame utilisant cette méthode était d’environ 7 images par seconde (ips).

Cependant, lorsque j'ai changé le code comme suit, le taux de trame a presque doublé à 15 images par seconde.

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

    // Various other operations
}
1
Karthik

Si vous utilisez VisualStudio 2005 ou 2008, pour obtenir les meilleures performances du vecteur, vous devez définir _ SECURE_SCL = 0

Par défaut, _SECURE_SCL est activé, ce qui rend l'itération du contenu beaucoup plus lente. Cela dit, laissez-le activé dans les versions de débogage, cela facilitera beaucoup le suivi des erreurs. Attention, étant donné que la macro modifie la taille des itérateurs et des conteneurs, vous devez être cohérent sur toutes les unités de compilation qui partagent un conteneur stl.

1
Stephen Nutt

Je pense que la seule réponse pourrait être un test sur votre plate-forme. Généralement, la seule chose normalisée dans la STL est le type d’itérateurs qu’offre une collection et la complexité des algorithmes.

Je dirais qu'il n'y a pas (pas beaucoup de différence) entre ces deux versions - la seule différence à laquelle je pouvais penser serait que le code doit parcourir l'ensemble de la collection lorsqu'il doit calculer la longueur d'un tableau (I ne sais pas si la longueur est stockée dans une variable à l'intérieur du vecteur, le temps système importerait peu)

Accéder aux éléments avec "at" devrait prendre un peu plus de temps que d'y accéder directement avec [] car il vérifie si vous êtes dans les limites du vecteur et lève une exception si vous êtes hors des limites (il semble que [] est normalement juste en utilisant l'arithmétique de pointeur - il devrait donc être plus rapide)

1
bernhardrusch

La différence devrait être négligeable. std :: vector garantit que ses éléments sont disposés de manière consécutive en mémoire. Par conséquent, la plupart des implémentations stl implémentent des itérateurs dans std :: vector en tant que pointeur normal. Dans cet esprit, la seule différence entre les deux versions devrait être que la première incrémente un pointeur, et dans la seconde incrémente un index qui est ensuite ajouté à un pointeur. Donc, je suppose que le second est peut-être une instruction machine extrêmement rapide (en termes de cycles).

Essayez de vérifier le code machine produit par votre compilateur.

En général, cependant, il serait conseillé de profiler si cela compte vraiment. Penser prématurément à ce genre de question ne vous rapporte généralement pas trop. Habituellement, les points névralgiques de votre code seront situés ailleurs, où vous ne le soupçonnerez peut-être pas à première vue.

0
Tobias

Seulement légèrement tangente à la question initiale, mais la boucle la plus rapide serait 

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

ce qui serait bien sûr compter à rebours. Cela donne une économie substantielle si vous avez un grand nombre d'itérations dans votre boucle, mais cela ne contient qu'un petit nombre d'opérations très rapides. 

Donc, avec l'accès opérateur [], cela pourrait être plus rapide que la plupart des exemples déjà publiés.

0
jam spandex

Voici un code que j'ai écrit, compilé dans Code :: Blocks v12.11, en utilisant le compilateur mingw par défaut . Cela crée un énorme vecteur, puis accède à chaque élément à l'aide d'itérateurs, de () et d'index . est bouclé une fois en appelant le dernier élément par fonction et une fois en enregistrant le dernier élément dans la mémoire temporaire.

Le chronométrage est effectué à l'aide de 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();
}

Sur cette base, j'ai personnellement constaté que les versions "optimisées" sont plus rapides que les "non optimisés". Les itérateurs sont plus lents que vector.at (), ce qui est plus lent que les index directs.

Je vous suggère de compiler et d'exécuter le code pour vous-mêmes.

EDIT: Ce code a été écrit lorsque j'avais moins d'expérience en C/C++. Un autre cas de test devrait consister à utiliser des opérateurs d'incrément de préfixe au lieu de postfix. Cela devrait améliorer le temps d'exécution.

0
ithenoob