Le moyen le plus rapide de réinitialiser chaque valeur de std :: vector <int> à 0

198

Quel est le moyen le plus rapide de réinitialiser chaque valeur de a std::vector<int>à 0 et de conserver la taille initiale des vecteurs?

Une boucle for avec l'opérateur []?

Matthieu Riegler
la source
4
std :: fill
Andriy Tylychko
1
"Le plus rapide" comme en performance? Ou comme dans le plus simple à mettre en œuvre / maintenir?
TheGeneral

Réponses:

341
std::fill(v.begin(), v.end(), 0);
Cat Plus Plus
la source
48
En regardant la sortie de l'assembly, gcc déroule réellement cette boucle en utilisant les registres mmx pour vider en 16 octets à la fois jusqu'à ce qu'elle se rapproche de la fin. Je dirais que c'est assez rapide. La version memset passe au memset, ce qui, je suppose, est à peu près aussi rapide. J'utiliserais ta méthode.
Omnifarious
Mais, passer à memset est une instruction unique, donc son utilisation entraînera une taille binaire plus petite.
Alexander Shishenko
2
ce n'est pas exactement ce qu'OP a demandé, mais simplement réaffecter votre vecteur à un nouveau de la même taille ( v = std::vector<int>(vec_size,0)) semble légèrement plus rapide que fillsur ma machine
Yibo Yang
1
C'est la façon la plus idiomatique de le faire, plus idiomatique que d'utiliser assign.
alfC
1
l'affectation à un nouveau vecteur fait-elle une allocation de tas? puis rejeter l'allocation du vecteur existant? Je pouvais voir que c'était plus lent que memset et al
Conrad Jones
150

Comme toujours lorsque vous posez la question la plus rapide: mesurez! En utilisant les méthodes ci-dessus (sur un Mac utilisant Clang):

Method      |  executable size  |  Time Taken (in sec) |
            |  -O0    |  -O3    |  -O0      |  -O3     |  
------------|---------|---------|-----------|----------|
1. memset   | 17 kB   | 8.6 kB  | 0.125     | 0.124    |
2. fill     | 19 kB   | 8.6 kB  | 13.4      | 0.124    |
3. manual   | 19 kB   | 8.6 kB  | 14.5      | 0.124    |
4. assign   | 24 kB   | 9.0 kB  | 1.9       | 0.591    |

en utilisant 100000 itérations sur un vecteur de 10000 pouces.

Modifier: si la modification de ces chiffres modifie de manière plausible les temps résultants, vous pouvez être certain (pas aussi bon que d'inspecter le code d'assemblage final) que le benchmark artificiel n'a pas été entièrement optimisé. Bien sûr, il est préférable de fausser les performances dans des conditions réelles. fin Modifier

pour référence le code utilisé:

#include <vector>

#define TEST_METHOD 1
const size_t TEST_ITERATIONS = 100000;
const size_t TEST_ARRAY_SIZE = 10000;

int main(int argc, char** argv) {

   std::vector<int> v(TEST_ARRAY_SIZE, 0);

   for(size_t i = 0; i < TEST_ITERATIONS; ++i) {
   #if TEST_METHOD == 1 
      memset(&v[0], 0, v.size() * sizeof v[0]);
   #elif TEST_METHOD == 2
      std::fill(v.begin(), v.end(), 0);
   #elif TEST_METHOD == 3
      for (std::vector<int>::iterator it=v.begin(), end=v.end(); it!=end; ++it) {
         *it = 0;
      }
   #elif TEST_METHOD == 4
      v.assign(v.size(),0);
   #endif
   }

   return EXIT_SUCCESS;
}

Conclusion: à utiliser std::fill(car, comme d'autres l'ont dit, c'est le plus idiomatique)!

Fabio Fracassi
la source
3
+1. Cette référence particulière n'est pas concluante, mais le point est absolument correct, vous devriez écrire un test de performance des alternatives car elles seront réellement utilisées. S'il n'y a pas de différence de performances, utilisez la source la plus simple.
Steve Jessop
3
"... pas concluant ..." IMO cette inconclusivité en soi est déjà un bon point pour faire des benchmarks, le plus souvent l'Optimizer fait déjà un très bon travail pour le genre de situations que le PO a demandé. Et je modifierais votre dernière phrase pour lire "S'il n'y a pas de différence de performance significative ..."
Fabio Fracassi
4
MISE À JOUR Utilisation de Nonius pour les tests de référence: clang3.6-libc ++ - c ++ 1y-O3 , gcc4.9-c ++ 1y-O3 et gcc5-c ++ 1y-O3 - TL; DR : assignest plus lent, sauf pour les petites capacités le libc++. CODE coliru / coller
sehe
2
De plus, wow, si vous vous souciez de la vitesse sans optimisations (ce qui pourrait être plausible si vous déployez en mode `` débogage '', ce que certaines équipes font), a l' fillair terrible. Il est de deux ordres de grandeur plus lent dans ce test.
Kyle Strand
5
@KyleStrand: Ce n'est pas que le remplissage soit terrible, c'est un modèle et le code est généré avec -O0 dans votre unité de traduction. Lorsque vous utilisez memset, vous utilisez le code libc qui a été compilé avec -O3 (même lorsque vous compilez votre code avec -O0). Si vous vous souciez de la vitesse de débogage et que vous utilisez des modèles, vous devrez utiliser une instanciation de modèle explicite dans un fichier séparé que vous compilez avec -O3
Tic
25

Et la assignfonction membre?

some_vector.assign(some_vector.size(), 0);
fredoverflow
la source
2
L'OP voulait réinitialiser les valeurs existantes, mais votre réponse est meilleure lorsque vous souhaitez redimensionner et réinitialiser les valeurs. Merci!
15

Si c'est juste un vecteur d'entiers, j'essaierais d'abord:

memset(&my_vector[0], 0, my_vector.size() * sizeof my_vector[0]);

Ce n'est pas très C ++, donc je suis sûr que quelqu'un fournira la bonne façon de le faire. :)

se détendre
la source
3
Étant donné que la norme (2003 TC1) garantit qu'un vecteur std :: est contigu en mémoire, cela devrait convenir. Si votre bibliothèque c ++ n'est pas conforme au TC1 2003, ne l'utilisez pas.
Mario
2
@Mario: Je n'aurais pas posté cela à moins que cela ne soit vrai et supposé bien connu, bien sûr. :) Mais merci.
détendez-vous le
1
J'ai vérifié l'assemblage. La ::std::fillméthode se développe pour quelque chose qui est sacrément rapide, mais un peu du côté du code car il est tout en ligne. Je l'emploierais quand même parce que c'est beaucoup plus agréable à lire.
Omnifarious
4
Vous feriez mieux d'ajouter vérifier si le vecteur est vide et ne rien faire dans ce cas. Le calcul de & buf [0] pour un vecteur vide peut générer des assertions dans le code STL.
Sergey
4

essayer

std::fill

et aussi

std::size siz = vec.size();
//no memory allocating
vec.resize(0);
vec.resize(siz, 0);
nttstar
la source
redimensionner est très agréable
Nick
3

J'avais la même question mais assez courte vector<bool>(afaik la norme permet de l'implémenter en interne différemment d'un simple tableau continu d'éléments booléens). J'ai donc répété les tests légèrement modifiés de Fabio Fracassi. Les résultats sont les suivants (fois, en secondes):

            -O0       -O3
         --------  --------
memset     0.666     1.045
fill      19.357     1.066
iterator  67.368     1.043
assign    17.975     0.530
for i     22.610     1.004

Donc apparemment pour ces tailles, vector<bool>::assign()c'est plus rapide. Le code utilisé pour les tests:

#include <vector>
#include <cstring>
#include <cstdlib>

#define TEST_METHOD 5
const size_t TEST_ITERATIONS = 34359738;
const size_t TEST_ARRAY_SIZE = 200;

using namespace std;

int main(int argc, char** argv) {

    std::vector<int> v(TEST_ARRAY_SIZE, 0);

    for(size_t i = 0; i < TEST_ITERATIONS; ++i) {
#if TEST_METHOD == 1
        memset(&v[0], false, v.size() * sizeof v[0]);
#elif TEST_METHOD == 2
        std::fill(v.begin(), v.end(), false);
   #elif TEST_METHOD == 3
        for (std::vector<int>::iterator it=v.begin(), end=v.end(); it!=end; ++it) {
            *it = 0;
        }
   #elif TEST_METHOD == 4
      v.assign(v.size(),false);
   #elif TEST_METHOD == 5
      for (size_t i = 0; i < TEST_ARRAY_SIZE; i++) {
          v[i] = false;
      }
#endif
    }

    return EXIT_SUCCESS;
}

J'ai utilisé le compilateur GCC 7.2.0 sur Ubuntu 17.10. La ligne de commande pour la compilation:

g++ -std=c++11 -O0 main.cpp
g++ -std=c++11 -O3 main.cpp
Yauhen Yakimenka
la source