web-dev-qa-db-fra.com

Accès rapide à un vecteur std :: par itérateur vs par opérateur [] / index?

Dis, j'ai un

std::vector<SomeClass *> v;

dans mon code et j'ai besoin d'accéder très souvent à ses éléments dans le programme, en les bouclant en avant et en arrière.

Quel est le type d'accès le plus rapide entre ces deux?

Accès itérateur:

std::vector<SomeClass *> v;
std::vector<SomeClass *>::iterator i;
std::vector<SomeClass *>::reverse_iterator j;

// i loops forward, j loops backward
for( i = v.begin(), j = v.rbegin(); i != v.end() && j != v.rend(); i++, j++ ){
    // some operations on v items
}

Accès en indice (par index)

std::vector<SomeClass *> v;
unsigned int i, j, size = v.size();

// i loops forward, j loops backward
for( i = 0, j = size - 1; i < size && j >= 0; i++, j-- ){
    // some operations on v items
}

Et, const_iterator offre-t-il un moyen plus rapide d'accéder aux éléments vectoriels au cas où je n'aurais pas à les modifier?

38

La différence de performances est probablement négligeable ou nulle (le compilateur peut les optimiser pour qu'elles soient identiques); vous devez vous soucier d'autres choses, par exemple si votre programme est correct (un programme lent mais correct vaut mieux qu'un programme rapide et incorrect). Il y a cependant d'autres avantages à utiliser des itérateurs, comme pouvoir changer le conteneur sous-jacent en un sans operator[] sans modifier vos boucles. Voir cette question pour en savoir plus.

const_iterators n'aura probablement aucune différence de performance, ou négligeable, par rapport aux itérateurs ordinaires. Ils sont conçus pour améliorer l'exactitude de votre programme en empêchant de modifier des choses qui ne devraient pas être modifiées, pas pour les performances. Il en va de même pour le mot clé const en général.

En bref, l'optimisation ne devrait pas vous préoccuper jusqu'à ce que deux choses se soient produites: 1) vous avez remarqué qu'elle s'exécute trop lentement et 2) vous avez profilé les goulots d'étranglement . Pour 1), s'il a fonctionné dix fois plus lentement qu'il ne le pourrait, mais ne s'exécute qu'une seule fois et prend 0,1 ms, qui s'en soucie? Pour 2), assurez-vous qu'il s'agit bien du goulot d'étranglement, sinon l'optimisation n'aura presque aucun effet mesurable sur les performances!

28
AshleysBrain

Un benchmark simple basé sur une boucle a été rempli. J'ai utilisé VS 2010 SP1 (configuration de version).

  1. Utilisez des itérateurs (* it = * it + 1;)
  2. Utilisez des indices (vs [i] = vs [i] + 1;)

En plusieurs milliards d'itérations, la deuxième approche s'est avérée un peu plus rapide, de 1%. Le résultat (les indices sont légèrement plus rapides que les itérateurs) est reproductible mais la différence, comme je l'ai dit, est très faible.

20
borx

Si la vitesse est importante, vous devriez avoir le temps et la volonté d'exécuter un profileur dessus et de voir ce qui fonctionne le mieux dans votre cas.

Si cela n'a pas d'importance, vous effectuez une optimisation prématurée et devez arrêter de le faire.

5
Joris Timmermans

J'ai eu un test hier, utilise [] vs itérateur, le code est de créer un vecteur avec quelques éléments et de supprimer certains éléments du vecteur. Ceci est le code utilise l'opérateur [] pour accéder aux éléments

  TimeSpent([](){
    std::vector<int> vt = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };
    for (int i = int(vt.size()) - 1; i >= 0; i--)
    {
      if (vt[i] % 2 == 0)
      {
        //cout << "removing " << vt[i] << endl;
        vt.erase(vt.begin() + i);
      }
    }
  });

Le code suivant concerne l'accès aux éléments vectoriels à l'aide de l'itérateur

  TimeSpent([](){
    std::vector<int> vt = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };
    for (std::vector<int>::iterator num = vt.begin(); num != vt.end();)
    {
      if (*num % 2 == 0)
      {
        num = vt.erase(num);
      }
      else
      {
        ++num;
      }
    }
  });

Testé en les appelant séparément par cette fonction

void TimeSpent(std::function<void()> func)
{
  const int ONE_MIL = 10000;
  long times = ONE_MIL;
  std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
  while (times > 0)
  {
    func();
    --times;
  }
  std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
  cout << "time elapsed : " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << endl;
}


L'environnement testé est Visual Studio 2013 Pro. version 4.5.51650
Les résultats sont les suivants:
Opérateur []: 192
itérateur: 212
Résumé: lorsque nous accédons au conteneur vectoriel, l'opérateur [] est plus rapide que l'itérateur.

4
r0ng

Je crois que les itérateurs vectoriels sont implémentés en tant que pointeurs en interne (dans une bonne implémentation STL), donc en général il devrait y avoir une différence de performances négligeable entre les deux idiomes. Mais si vous voulez savoir comment cela fonctionne sur la plate-forme votre, pourquoi ne le mesurez-vous pas avec un petit programme de test? Je ne pense pas qu'il faudrait plus de 5 minutes pour mesurer le temps d'exécution, par ex. 1 million d'itérations avec les deux variantes ...

2
Péter Török

Avec l'optimisation (-O2), les timings devraient s'améliorer (devraient être presque identiques).

1
Jay

Comme toujours, cela dépend. Normalement, je ne pense pas que vous verriez une différence, mais vous seul pouvez le déterminer en profilant votre code. Certains compilateurs implémentent des itérateurs vectoriels comme pointeurs bruts, et d'autres non. De plus, dans les versions de débogage, certains compilateurs peuvent utiliser un itérateur vérifié, qui peut être plus lent. Mais en mode production, cela ne peut pas être différent. Profilez-le et voyez.

1
Brian Neal

J'étais confus à propos de quelque chose de similaire et j'ai écrit un programme pour tester les performances: https://github.com/rajatkhanduja/Benchmarks/blob/master/C%2B%2B/vectorVsArray.cpp

Voici les observations pertinentes pour lire/écrire sur le vecteur <int> de taille 1m en utilisant g ++ (sans indicateur d'optimisation), sur Linux-i686 (machine 64 bits) avec 7,7 Go de RAM: -

Temps nécessaire pour écrire sur un vecteur à l'aide d'indices. : 11,3909 ms

Temps nécessaire pour lire le vecteur à l'aide d'indices, séquentiellement. : 4,09106 ms

Temps de lecture du vecteur à l'aide d'indices, de manière aléatoire. : 39 ms

Temps nécessaire pour écrire sur un vecteur à l'aide d'itérateurs (séquentiellement). : 24,9949 ms

Temps de lecture du vecteur à l'aide d'itérateurs (séquentiellement). : 18,8049 ms

0
rajatkhanduja

En termes de vitesse, je pense que presque même chose. Mieux, vous pouvez profiler et vérifier de toute façon.

Au moins, vous pouvez réduire le nombre de variables utilisées :)

for( i = 0; i < size ; i++){
    // some operations on v items
    v[i];
    v[size-i+1];
}

À propos de const_iterator: Pls référer mon Q: A re const_iterators plus rapide?

0
aJ.

J'irais pour les itérateurs, mais ce que j'optimiserais, c'est d'appeler end() dans la boucle et changerais preincrement en postincrement. C'est à dire. Je

std::vector<SomeClass *> v;
std::vector<SomeClass *>::iterator i,ie;
std::vector<SomeClass *>::reverse_iterator j,je;

// i loops forward, j loops backward
for( i=v.begin(),ie=v.end(), j=v.rbegin(),je=v.rend(); i!=ie && j!=je; ++i,++j ){
    // some operations on v items
}

Et je ne pense pas que ce soit une microoptimisation prématurée, il s'agit simplement d'écrire un meilleur code. Beaucoup moins de mal que d'appeler à chaque tentative d'écriture de microoptimisation prématurée de code efficace et de remplacer la pensée par le profilage.