Il modo più veloce per ripristinare tutti i valori di std :: vector <int> su 0


Risposte:


341
std::fill(v.begin(), v.end(), 0);

48
Osservando l'output dell'assembly, gcc in realtà svolge questo loop nell'uso dei registri mmx per scaricare in 16 byte alla volta fino a quando non si avvicina alla fine. Direi che è abbastanza veloce. La versione di memset passa a memset, che suppongo sia altrettanto veloce. Userei il tuo metodo.
Onnipotente il

Ma saltare a memset è una singola istruzione, quindi usarlo comporterà una dimensione binaria più piccola.
Alexander Shishenko,

2
questo non è esattamente ciò che OP ha richiesto, ma semplicemente riassegnare il tuo vettore a uno nuovo della stessa dimensione ( v = std::vector<int>(vec_size,0)) sembra leggermente più veloce rispetto fillalla mia macchina
Yibo Yang

1
Questo è il modo più idiomatico di farlo, più idiomatico dell'uso assign.
alfC

1
assegnarlo a un nuovo vettore fa l'allocazione dell'heap? e quindi scartare l'allocazione del vettore esistente? Potevo vedere che essendo più lento di memset et al
Conrad Jones il

150

Come sempre quando chiedi il più veloce: misura! Usando i metodi sopra (su un Mac usando 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    |

usando 100000 iterazioni su un vettore di 10000 ints.

Modifica: se la modifica di questi numeri modifica plausibilmente i tempi risultanti, si può avere una certa sicurezza (non buona come ispezionare il codice dell'assemblaggio finale) che il benchmark artificiale non è stato completamente ottimizzato. Naturalmente è meglio messaggiare le prestazioni in condizioni reali. fine Modifica

per riferimento il codice utilizzato:

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

Conclusione: usare std::fill(perché, come altri hanno già detto più idiomatico)!


3
+1. Questo particolare benchmark non è conclusivo, ma il punto è assolutamente corretto, dovresti scrivere un test delle prestazioni delle alternative in quanto verranno effettivamente utilizzate. Se non ci sono differenze di prestazioni, utilizzare la fonte più semplice da utilizzare.
Steve Jessop,

3
"... non conclusivo ..." IMO questa incoerenza in sé è già un buon punto per fare benchmark, il più delle volte l'Ottimizzatore fa già un ottimo lavoro per il tipo di situazioni poste dall'OP. E modificherei la tua ultima frase per leggere "Se non ci sono differenze significative nelle prestazioni ..."
Fabio Fracassi,

4
AGGIORNAMENTO Uso di Nonius per i benchmark: clang3.6-libc ++ - c ++ 1y-O3 , gcc4.9-c ++ 1y-O3 e gcc5-c ++ 1y-O3 - TL; DR : assignè più lento, tranne per le piccole capacità su libc++. CODICE coliru / paste
vedi

2
Inoltre, wow, se ti preoccupi della velocità senza ottimizzazioni (che potrebbe essere plausibile se stai implementando in modalità 'debug', come fanno alcuni team), fillsembra terribile. È due ordini di grandezza più lenti in questo test.
Kyle Strand,

5
@KyleStrand: non è che il riempimento sia terribile, è un modello e il codice viene generato con -O0 all'interno dell'unità di traduzione. Quando usi memset, stai usando il codice libc che è stato compilato con -O3 (anche quando compili il tuo codice con -O0). Se ti interessa la velocità nel debug e usi i template, dovrai usare un'istanza esplicita dei template in un file separato che compili con -O3
Tic

25

Che ne dici della assignfunzione membro?

some_vector.assign(some_vector.size(), 0);

2
L'OP voleva ripristinare i valori esistenti, ma la risposta è migliore quando si desidera ridimensionare e ripristinare i valori. Grazie!

15

Se è solo un vettore di numeri interi, prima proverei:

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

Non è molto C ++, quindi sono sicuro che qualcuno fornirà il modo corretto di farlo. :)


3
Poiché lo standard (TC1 2003) garantisce che uno std :: vector è contiguo in memoria, questo dovrebbe andare bene. Se la tua libreria c ++ non è conforme al TC1 del 2003, non usarlo.
Mario,

2
@Mario: non avrei pubblicato questo a meno che ciò non fosse vero e presumibilmente noto, ovviamente. :) Ma grazie.
Rilassati il

1
Ho controllato il montaggio. Il ::std::fillmetodo si espande in qualcosa che è piuttosto dannatamente veloce, anche se un po 'sul lato del codice bloaty poiché è tutto in linea. Lo userei comunque perché è molto più bello da leggere.
Onnipotente il

4
Faresti meglio ad aggiungere controlla se il vettore è vuoto e non fare nulla in questo caso. Calcolare & buf [0] per un vettore vuoto può generare asserzioni nel codice STL.
Sergey

4

provare

std::fill

e anche

std::size siz = vec.size();
//no memory allocating
vec.resize(0);
vec.resize(siz, 0);

il ridimensionamento è molto bello
Nick,

3

Avevo la stessa domanda, ma piuttosto breve vector<bool>(afaik lo standard consente di implementarlo internamente in modo diverso rispetto a una semplice serie continua di elementi booleani). Quindi ho ripetuto i test leggermente modificati di Fabio Fracassi. I risultati sono i seguenti (tempi, in secondi):

            -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

Quindi apparentemente per queste dimensioni, vector<bool>::assign()è più veloce. Il codice utilizzato per i test:

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

Ho usato il compilatore GCC 7.2.0 su Ubuntu 17.10. La riga di comando per la compilazione:

g++ -std=c++11 -O0 main.cpp
g++ -std=c++11 -O3 main.cpp
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.