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.