Dans notre cours sur C++, ils suggèrent de ne plus utiliser de tableaux C++ sur de nouveaux projets. Autant que je sache, Stroustroup lui-même suggère de ne pas utiliser de tableaux. Mais existe-t-il des différences de performances significatives?
L'utilisation de tableaux C++ avec new
(en d'autres termes, l'utilisation de tableaux dynamiques) doit être évitée. Il y a le problème que vous devez garder une trace de la taille, vous devez les supprimer manuellement et faire toutes sortes de tâches ménagères.
L'utilisation de tableaux sur la pile est également déconseillée, car vous n'avez pas de vérification de plage. Si vous passez le tableau, vous perdez toutes les informations sur sa taille (conversion de tableau en pointeur). Dans ce cas, vous devriez utiliser boost::array
, qui encapsule un tableau C++ dans une petite classe et fournit une fonction size
et des itérateurs pour le parcourir.
Maintenant, les tableaux std :: vector et les tableaux C++ natifs (tirés d’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.
Remarque: Si vous allouez des tableaux avec new
et allouez des objets ne faisant pas partie de la classe (comme plain int
) ou des classes sans constructeur défini par l'utilisateur et, vous ne voulez pas que vos éléments soient initialement initialisés. , en utilisant new
- Les tableaux alloués peuvent avoir des avantages en termes de performances car std::vector
initialise tous les éléments aux valeurs par défaut (0 pour int, par exemple) lors de la construction (merci à @bernie de se souvenir de moi).
Rappelles toi:
"Les programmeurs perdent énormément de temps à réfléchir à la vitesse des parties non critiques de leurs programmes ou à s'inquiéter de leur rapidité. Ces tentatives d'efficacité ont en réalité un impact très négatif sur le débogage et la maintenance. Nous devrions oublier les petits gains d'efficacité, disons 97% du temps: l'optimisation prématurée est la racine de tout Mal. Pourtant, nous ne devrions pas laisser passer nos opportunités dans ces 3% critiques ".
(Merci à metamorphosis pour la citation complète)
N'utilisez pas un tableau C au lieu d'un vecteur (ou autre) simplement parce que vous pensez qu'il est plus rapide, car il est supposé être de niveau inférieur. Vous auriez tort.
Utilisez vector par défaut (ou le conteneur sécurisé adapté à vos besoins), puis si votre profileur vous dit que c'est un problème, voyez si vous pouvez l'optimiser, soit en utilisant un meilleur algorithme, soit en modifiant le conteneur.
Cela dit, nous pouvons revenir à la question initiale.
Les classes de tableaux C++ se comportent mieux que les tableaux C de bas niveau, car elles en savent beaucoup sur elles-mêmes et peuvent répondre aux questions que les tableaux C ne peuvent pas. Ils sont capables de nettoyer après eux-mêmes. Et plus important encore, ils sont généralement écrits à l'aide de modèles et/ou d'inline, ce qui signifie que ce qui apparaît dans beaucoup de code dans le débogage se résume à peu ou pas de code produit dans la version finale, ce qui signifie qu'il n'y a aucune différence avec leur concurrence moins sécurisée intégrée.
Au total, il tombe sur deux catégories:
L'utilisation d'un pointeur sur un tableau malloc-ed/new-ed sera au mieux aussi rapide que la version std :: vector, et beaucoup moins sûre (voir la publication de litb ).
Donc utilisez un vecteur :: std.
Utiliser un tableau statique sera au mieux:
Donc utilisez un std :: array .
Parfois, l’utilisation de vector
au lieu d’un tampon brut entraîne un coût visible, car vector
initialisera le tampon lors de la construction, alors que le code qu’il remplace ne l’a pas été, comme indiqué par bernie by dans sa answer .
Si tel est le cas, vous pouvez le gérer en utilisant un unique_ptr
au lieu d'un vector
ou, si le cas n'est pas exceptionnel dans votre code, écrivez en fait une classe buffer_owner
qui possédera cette mémoire et vous donnera un accès facile et sûr à y compris des bonus comme le redimensionnement (en utilisant realloc
?), ou ce dont vous avez besoin.
Les vecteurs sont des tableaux sous le capot. La performance est la même.
Un endroit où vous pouvez rencontrer un problème de performance est de ne pas dimensionner correctement le vecteur pour commencer.
Lorsqu'un vecteur se remplit, il se redimensionne, ce qui peut impliquer une nouvelle allocation de tableau, suivie de n constructeurs de copie, suivie d'environ n appels de destructeurs, suivie d'une suppression de tableau.
Si votre construction/destruction est coûteuse, il est beaucoup mieux de commencer par définir le vecteur.
Il existe un moyen simple de le démontrer. Créez une classe simple qui indique quand elle est construite/détruite/copiée/assignée. Créez un vecteur de ces choses et commencez à les pousser à l’arrière du vecteur. Lorsque le vecteur se remplit, il y aura une cascade d’activités lorsque le vecteur sera redimensionné. Ensuite, essayez à nouveau avec le vecteur dimensionné au nombre d'éléments souhaité. Vous verrez la différence.
Pour répondre à quelque chose Mehrdad dit:
Cependant, il peut y avoir des cas où vous avez encore besoin de tableaux. Quand interfaçage avec du code de bas niveau (c'est-à-dire. Assemblée) ou d'anciennes bibliothèques nécessiter des tableaux, vous pourriez ne pas pouvoir utiliser des vecteurs.
Pas vrai du tout. Les vecteurs se dégradent bien en tableaux/pointeurs si vous utilisez:
vector<double> vector;
vector.Push_back(42);
double *array = &(*vector.begin());
// pass the array to whatever low-level code you have
Cela fonctionne pour toutes les principales implémentations STL. Dans la prochaine norme, il sera nécessaire de travailler (même si cela se passe très bien aujourd'hui).
Vous avez encore moins de raisons d'utiliser des tableaux simples en C++ 11.
Il existe 3 types de tableaux dans la nature, du plus rapide au plus lent, en fonction des fonctionnalités dont ils disposent (bien entendu, la qualité de la mise en œuvre peut rendre les choses très rapides, même dans le cas 3 de la liste):
std::array<T, N>
dynarray
dans C++ TS après C++ 14. En C il y a des VLA std::vector<T>
Pour 1. tableaux statiques simples avec un nombre fixe d'éléments, utilisez std::array<T, N>
en C++ 11.
Pour 2., les tableaux de taille fixe spécifiés au moment de l'exécution, mais cela ne changera pas leur taille, il y a une discussion dans C++ 14 mais cela a été déplacé vers une spécification technique et finalement créé à partir de C++ 14.
Pour 3.std::vector<T>
demandera généralement de la mémoire dans le tas. Cela pourrait avoir des conséquences sur les performances, bien que vous puissiez utiliser std::vector<T, MyAlloc<T>>
pour améliorer la situation avec un allocateur personnalisé. L'avantage par rapport à T mytype[] = new MyType[n];
est que vous pouvez le redimensionner et qu'il ne se désintégrera pas en un pointeur, contrairement aux tableaux simples.
Utilisez les types de bibliothèque standard mentionnés pour éviter les tableaux se décomposent en pointeurs . Vous économiserez du temps de débogage et les performances seront exactement identiques à celles des baies standard si vous utilisez le même ensemble de fonctionnalités.
Aller avec STL. Il n'y a pas de pénalité de performance. Les algorithmes sont très efficaces et permettent de gérer des types de détails auxquels la plupart d’entre nous ne penseraient pas.
A propos de la contribution de duli .
La conclusion est que les tableaux d'entiers sont plus rapides que les vecteurs d'entiers (5 fois dans mon exemple). Cependant, les tableaux et les vecteurs ont une vitesse identique à celle des données plus complexes/non alignées.
STL est une bibliothèque fortement optimisée. En fait, il est même suggéré d'utiliser STL dans des jeux où des performances élevées pourraient être nécessaires. Les tableaux sont trop sujets aux erreurs pour être utilisés dans les tâches quotidiennes. Les compilateurs actuels sont également très intelligents et peuvent vraiment produire un excellent code avec STL. Si vous savez ce que vous faites, STL peut généralement fournir les performances nécessaires. Par exemple, en initialisant les vecteurs à la taille requise (si vous le savez bien au début), vous pouvez atteindre les performances de la matrice. Cependant, il peut arriver que vous ayez encore besoin de tableaux. Lors de l'interfaçage avec du code de bas niveau (c'est-à-dire Assembly) ou d'anciennes bibliothèques nécessitant des tableaux, vous ne pourrez peut-être pas utiliser de vecteurs.
Si vous compilez le logiciel en mode débogage, de nombreux compilateurs n'aligneront pas les fonctions d'accès du vecteur. Cela ralentira considérablement la mise en œuvre du vecteur stl dans les cas où les performances sont un problème. Cela facilitera également le débogage du code puisque vous pouvez voir dans le débogueur combien de mémoire a été allouée.
En mode optimisé, le vecteur stl devrait s’approcher de l’efficacité d’un tableau. C'est parce que beaucoup de méthodes vectorielles sont maintenant en ligne.
L’utilisation de std::vector
par rapport à un tableau brut a un impact sur les performances lorsque vous souhaitez un tampon non initialisé (par exemple, à utiliser comme destination pour memcpy()
). Un std::vector
initialisera tous ses éléments en utilisant le constructeur par défaut. Un tableau brut ne sera pas.
La spécification c ++ du constructeur std:vector
prenant un argument count
(c'est la troisième forme) indique:
`Construit un nouveau conteneur à partir de diverses sources de données, en utilisant éventuellement un allocator alloc fourni par l'utilisateur.
3) Construit le conteneur avec le nombre d'instances de T. insérées par défaut. Aucune copie n'est effectuée.
Complexité
2-3) Linéaire en nombre
Un tableau brut n'engendre pas ce coût d'initialisation.
Voir aussi Comment éviter std :: vector <> pour initialiser tous ses éléments?
La différence de performances entre les deux dépend beaucoup de l'implémentation - si vous comparez un std :: vector mal implémenté à une implémentation de tableau optimale, le tableau gagnerait, mais inversez-le et le vecteur gagnerait ...
Tant que vous comparez des pommes avec des pommes (le tableau et le vecteur ont tous deux un nombre fixe d’éléments, ou les deux sont redimensionnés de façon dynamique), je penserais que la différence de performances est négligeable tant que vous suivez la pratique de codage STL. N'oubliez pas que l'utilisation de conteneurs C++ standard vous permet également d'utiliser les algorithmes prédéfinis qui font partie de la bibliothèque C++ standard et que la plupart d'entre eux sont probablement plus performants que l'implémentation moyenne du même algorithme que vous construisez vous-même. .
Cela dit, le vecteur IMHO gagne dans un scénario de débogage avec un STL de débogage, car la plupart des implémentations de STL avec un mode de débogage approprié peuvent au moins mettre en évidence/corriger les erreurs typiques commises par des personnes travaillant avec des conteneurs standard.
Oh, et n'oubliez pas que le tableau et le vecteur partagent la même disposition de mémoire afin que vous puissiez utiliser des vecteurs pour transmettre des données au code C ou C++ hérité qui attend des tableaux de base. Gardez à l'esprit que la plupart des paris sont dans ce scénario, cependant, et vous avez à nouveau affaire à de la mémoire brute.
Le test simple suivant:
Explication du test de performance des tableaux C++ vs Vector
contredit les conclusions de "Comparaison du code Assembly généré pour les opérations d'indexation, de déréférencement et d'incrémentation de base sur les vecteurs et les tableaux/pointeurs".
Il doit y avoir une différence entre les tableaux et les vecteurs. Le test dit alors ... essayez, le code est là ...
Il peut arriver que vous ayez un accès vectoriel dans une fonction inline dans une fonction inline, où vous êtes allé au-delà de ce que le compilateur alignera et forcera un appel de fonction. Ce serait assez rare pour ne pas vous inquiéter - en général, je serais d’accord avec litb .
Je suis surpris que personne n’en ait encore parlé - ne vous inquiétez pas des performances tant qu’il n’a pas été prouvé que c’était un problème, puis comparez.
Les vecteurs utilisent un peu plus de mémoire que les tableaux car ils contiennent la taille du tableau. Ils augmentent également la taille du disque dur des programmes et probablement l’empreinte mémoire des programmes. Ces augmentations sont minimes, mais peuvent avoir de l'importance si vous travaillez avec un système intégré. Bien que la plupart des endroits où ces différences sont importantes soient des endroits où vous utiliseriez C plutôt que C++.
Parfois, les tableaux sont en effet meilleurs que les vecteurs. Si vous manipulez toujours Un ensemble d'objets de longueur fixe, les tableaux sont mieux. Prenez en compte les extraits de code suivants:
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;
}
où la version vectorielle de X est
class X {
vector<int> vec;
public:
X(const vector<int>& v) {vec = v;}
int first() { return vec[0];}
};
et la version de tableau de X est:
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];}
};
La version tableau de main () sera plus rapide car nous évitons l’overhead de "nouveau" à chaque fois dans la boucle interne.
(Ce code a été posté sur comp.lang.c ++ par moi).
Je dirais que la principale préoccupation n'est pas la performance, mais la sécurité. Vous pouvez faire beaucoup d'erreurs avec les tableaux (pensez à redimensionner, par exemple), où un vecteur vous éviterait beaucoup de douleur.
Si vous utilisez des vecteurs pour représenter un comportement multidimensionnel, les performances sont affectées.
Les vecteurs 2d + provoquent-ils des pertes de performances?
En résumé, il y a une petite quantité de temps système avec chaque information sur la taille de chaque sous-vecteur, et il n'y aura pas nécessairement de sérialisation des données (comme c'est le cas avec les tableaux multidimensionnels c). Ce manque de sérialisation peut offrir plus que des possibilités d'optimisation micro. Si vous utilisez des tableaux multidimensionnels, il peut être préférable de simplement étendre std :: vector et de lancer votre propre fonction get/set/resize bits.
Si vous n'avez pas besoin d'ajuster la taille de manière dynamique, vous avez la surcharge de mémoire liée à la sauvegarde de la capacité (un pointeur/size_t). C'est tout.
En supposant un tableau de longueur fixe (par exemple, int* v = new int[1000];
vs std::vector<int> v(1000);
, la taille de v
étant maintenue à 1 000), la seule considération de performance qui compte vraiment (ou du moins qui importait pour moi quand j'étais dans un dilemme similaire) accès à un élément. J'ai cherché le code vectoriel de la STL et voici ce que j'ai trouvé:
const_reference
operator[](size_type __n) const
{ return *(this->_M_impl._M_start + __n); }
Cette fonction sera très certainement intégrée par le compilateur. Donc, tant que la seule chose que vous envisagez de faire avec v
est d'accéder à ses éléments avec operator[]
, il semble qu'il ne devrait pas y avoir vraiment de différence de performance.