Std :: vector è molto più lento degli array semplici?


212

Ho sempre pensato che fosse la saggezza generale che std::vectorè "implementata come una matrice", bla bla bla. Oggi sono andato giù e l'ho provato, e sembra non essere così:

Ecco alcuni risultati del test:

UseArray completed in 2.619 seconds
UseVector completed in 9.284 seconds
UseVectorPushBack completed in 14.669 seconds
The whole thing completed in 26.591 seconds

Questo è circa 3-4 volte più lento! Non giustifica davvero i vectorcommenti " potrebbe essere più lento per alcuni nanosec".

E il codice che ho usato:

#include <cstdlib>
#include <vector>

#include <iostream>
#include <string>

#include <boost/date_time/posix_time/ptime.hpp>
#include <boost/date_time/microsec_time_clock.hpp>

class TestTimer
{
    public:
        TestTimer(const std::string & name) : name(name),
            start(boost::date_time::microsec_clock<boost::posix_time::ptime>::local_time())
        {
        }

        ~TestTimer()
        {
            using namespace std;
            using namespace boost;

            posix_time::ptime now(date_time::microsec_clock<posix_time::ptime>::local_time());
            posix_time::time_duration d = now - start;

            cout << name << " completed in " << d.total_milliseconds() / 1000.0 <<
                " seconds" << endl;
        }

    private:
        std::string name;
        boost::posix_time::ptime start;
};

struct Pixel
{
    Pixel()
    {
    }

    Pixel(unsigned char r, unsigned char g, unsigned char b) : r(r), g(g), b(b)
    {
    }

    unsigned char r, g, b;
};

void UseVector()
{
    TestTimer t("UseVector");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel> pixels;
        pixels.resize(dimension * dimension);

        for(int i = 0; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }
    }
}

void UseVectorPushBack()
{
    TestTimer t("UseVectorPushBack");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel> pixels;
            pixels.reserve(dimension * dimension);

        for(int i = 0; i < dimension * dimension; ++i)
            pixels.push_back(Pixel(255, 0, 0));
    }
}

void UseArray()
{
    TestTimer t("UseArray");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        Pixel * pixels = (Pixel *)malloc(sizeof(Pixel) * dimension * dimension);

        for(int i = 0 ; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }

        free(pixels);
    }
}

int main()
{
    TestTimer t1("The whole thing");

    UseArray();
    UseVector();
    UseVectorPushBack();

    return 0;
}

Sto sbagliando o qualcosa del genere? O ho appena rotto questo mito della performance?

Sto usando la modalità di rilascio in Visual Studio 2005 .


In Visual C ++ , si #define _SECURE_SCL 0riduce UseVectordella metà (riducendolo a 4 secondi). Questo è davvero enorme, IMO.


23
Alcune versioni di vector quando si è in modalità debug aggiungono ulteriori istruzioni per verificare che non si acceda oltre la fine dell'array e cose del genere. Per ottenere tempi reali è necessario costruire in modalità di rilascio e attivare le ottimizzazioni.
Martin York,

40
È positivo che tu abbia misurato invece di credere alle affermazioni che hai sentito su Internet.
P:

51
il vettore è implementato come un array. Questa non è "saggezza convenzionale", è la verità. Hai scoperto che vectorè un array ridimensionabile per tutti gli usi. Congratulazioni. Come per tutti gli strumenti generici, è possibile trovare situazioni specializzate in cui non è ottimale. Questo è il motivo per cui la saggezza convenzionale è di iniziare con un vectore considerare alternative se necessario.
Dennis Zickefoose,

37
lol, qual è la differenza di velocità tra "gettare i piatti sporchi nel lavandino" e "gettare i piatti sporchi nel lavandino e controllare se non si sono rotti"?
Imre L

9
Almeno su VC2010 sembra che la differenza principale sia che malloc () è più veloce di resize (). Rimuovi l'allocazione di memoria dai tempi, compila con _ITERATOR_DEBUG_LEVEL == 0 e i risultati sono gli stessi.
Andreas Magnusson,

Risposte:


260

Utilizzando quanto segue:

g ++ -O3 Time.cpp -I <MyBoost>
./a.out
UseArray completato in 2.196 secondi
UseVector completato in 4.412 secondi
UseVectorPushBack completato in 8.017 secondi
Il tutto completato in 14.626 secondi

Quindi l'array è due volte più veloce del vettore.

Ma dopo aver esaminato il codice in modo più dettagliato ciò è previsto; mentre attraversi il vettore due volte e l'array una sola volta. Nota: quando si è resize()il vettore non si sta solo allocando la memoria ma si esegue anche il vettore e si chiama il costruttore su ciascun membro.

Riorganizzare leggermente il codice in modo che il vettore inizializzi ogni oggetto una sola volta:

 std::vector<Pixel>  pixels(dimensions * dimensions, Pixel(255,0,0));

Ora ripetiamo lo stesso tempismo:

g ++ -O3 Time.cpp -I <MyBoost>
./a.out
UseVector completato in 2.216 secondi

Il vettore ora ha prestazioni solo leggermente peggiori dell'array. IMO questa differenza è insignificante e potrebbe essere causata da un sacco di cose non associate al test.

Vorrei anche tenere conto del fatto che non si sta inizializzando / distruggendo correttamente l'oggetto Pixel nel UseArrray()metodo in quanto non viene chiamato né costruttore / distruttore (questo potrebbe non essere un problema per questa semplice classe ma qualcosa di leggermente più complesso (cioè con puntatori o membri) con puntatori) causerà problemi.


48
@ kizzx2: devi usare reserve()invece di resize(). Ciò alloca spazio per gli oggetti (ovvero modifica la capacità del vettore) ma non crea gli oggetti (ovvero la dimensione del vettore rimane invariata).
James McNellis,

25
Stai effettuando 1 000 000 000 accessi di array. La differenza oraria è di 0,333 secondi. O una differenza di 0,000000000333 per accesso all'array. Supponendo che un processore da 2,33 GHz come il mio abbia 0,7 fasi di pipeline di istruzioni per accesso ad array. Quindi il vettore sembra che stia utilizzando un'istruzione aggiuntiva per accesso.
Martin York,

3
@James McNellis: Non si può semplicemente sostituire resize()con reserve(), perché questo non regola idea interna del vettore di una propria dimensione, scrive così successive ai suoi elementi tecnicamente "scrivere dopo la fine" e produrrà UB. Sebbene in pratica ogni implementazione di STL "si comporterà" al riguardo, come si risincronizzano le dimensioni del vettore? Se provi a chiamare resize() dopo aver popolato il vettore, molto probabilmente sovrascriverà tutti quegli elementi con valori predefiniti!
j_random_hacker

8
@j_random_hacker: Non è esattamente quello che ho detto? Pensavo di essere molto chiaro che reservecambia solo la capacità di un vettore, non le sue dimensioni.
James McNellis,

7
Va bene, vai a capire. C'erano molte maglie legate alle eccezioni nei metodi vettoriali. L'aggiunta /EHscalle opzioni di compilazione lo ha ripulito e assign()ora batte l'array. Sìì.
Pavel Minaev,

55

Ottima domanda Sono venuto qui aspettandomi di trovare qualche semplice soluzione che accelerasse i test dei vettori. Non ha funzionato come mi aspettavo!

L'ottimizzazione aiuta, ma non è sufficiente. Con l'ottimizzazione su sto ancora vedendo una differenza di prestazioni 2X tra UseArray e UseVector. È interessante notare che UseVector era significativamente più lento di UseVectorPushBack senza ottimizzazione.

# g++ -Wall -Wextra -pedantic -o vector vector.cpp
# ./vector
UseArray completed in 20.68 seconds
UseVector completed in 120.509 seconds
UseVectorPushBack completed in 37.654 seconds
The whole thing completed in 178.845 seconds
# g++ -Wall -Wextra -pedantic -O3 -o vector vector.cpp
# ./vector
UseArray completed in 3.09 seconds
UseVector completed in 6.09 seconds
UseVectorPushBack completed in 9.847 seconds
The whole thing completed in 19.028 seconds

Idea n. 1: usa new [] invece di malloc

Ho provato a cambiare malloc()a new[]in UseArray modo che gli oggetti otterrebbe costruito. E passando dall'assegnazione dei singoli campi all'assegnazione di un'istanza Pixel. Oh, e rinominando la variabile loop interna in j.

void UseArray()
{
    TestTimer t("UseArray");

    for(int i = 0; i < 1000; ++i)
    {   
        int dimension = 999;

        // Same speed as malloc().
        Pixel * pixels = new Pixel[dimension * dimension];

        for(int j = 0 ; j < dimension * dimension; ++j)
            pixels[j] = Pixel(255, 0, 0);

        delete[] pixels;
    }
}

Sorprendentemente (per me), nessuna di queste modifiche ha fatto alcuna differenza. Nemmeno la modifica a new[]cui per impostazione predefinita costruirà tutti i pixel. Sembra che gcc possa ottimizzare le chiamate predefinite del costruttore durante l'uso new[], ma non durante l'uso vector.

Idea n. 2: rimuove le chiamate ripetute dell'operatore []

Ho anche tentato di sbarazzarmi della tripla operator[]ricerca e memorizzare nella cache il riferimento pixels[j]. Ciò ha effettivamente rallentato UseVector! Ops.

for(int j = 0; j < dimension * dimension; ++j)
{
    // Slower than accessing pixels[j] three times.
    Pixel &pixel = pixels[j];
    pixel.r = 255;
    pixel.g = 0;
    pixel.b = 0;
}

# ./vector 
UseArray completed in 3.226 seconds
UseVector completed in 7.54 seconds
UseVectorPushBack completed in 9.859 seconds
The whole thing completed in 20.626 seconds

Idea n. 3: rimuovere i costruttori

Che ne dici di rimuovere completamente i costruttori? Quindi forse gcc può ottimizzare la costruzione di tutti gli oggetti quando vengono creati i vettori. Cosa succede se cambiamo Pixel in:

struct Pixel
{
    unsigned char r, g, b;
};

Risultato: circa il 10% più veloce. Ancora più lento di un array. Hm.

# ./vector 
UseArray completed in 3.239 seconds
UseVector completed in 5.567 seconds

Idea n. 4: utilizzare iteratore anziché indice di loop

Che ne dici di usare un vector<Pixel>::iteratorindice invece di un ciclo?

for (std::vector<Pixel>::iterator j = pixels.begin(); j != pixels.end(); ++j)
{
    j->r = 255;
    j->g = 0;
    j->b = 0;
}

Risultato:

# ./vector 
UseArray completed in 3.264 seconds
UseVector completed in 5.443 seconds

No, non diverso. Almeno non è più lento. Ho pensato che questo avrebbe avuto prestazioni simili al n. 2 in cui ho usato un Pixel&riferimento.

Conclusione

Anche se alcuni cookie intelligenti capiscono come rendere il ciclo vettoriale più veloce di quello dell'array, questo non parla bene del comportamento predefinito di std::vector. Tanto per il compilatore che è abbastanza intelligente da ottimizzare tutto il C ++ e rendere i contenitori STL veloci come array grezzi.

La linea di fondo è che il compilatore non è in grado di ottimizzare le chiamate del costruttore predefinito no-op durante l'utilizzo std::vector. Se usi il semplice new[], li ottimizza bene. Ma non con std::vector. Anche se puoi riscrivere il tuo codice per eliminare le chiamate del costruttore che volano di fronte al mantra qui intorno: "Il compilatore è più intelligente di te. L'STL è veloce quanto il semplice C. Non preoccuparti."


2
Ancora una volta, grazie per aver effettivamente eseguito il codice. A volte è facile essere colpiti senza motivo quando qualcuno tenta di sfidare le opinioni popolari.
kizzx2,

3
"Tanto per il compilatore che è abbastanza intelligente da ottimizzare tutto il C ++ e rendere i contenitori STL veloci come array grezzi." Bei commenti. Ho una teoria secondo cui questo "compilatore è intelligente" è solo un mito: l'analisi del C ++ è estremamente difficile e il compilatore è solo una macchina.
kizzx2,

3
Non so. Certo, è stato in grado di rallentare il test dell'array, ma non ha accelerato quello vettoriale. Ho modificato sopra dove ho rimosso i costruttori da Pixel e l'ho reso una semplice struttura, ed era ancora lento. Questa è una brutta notizia per chiunque usi tipi semplici come vector<int>.
John Kugelman,

2
Vorrei davvero poter votare due volte la tua risposta. Idee intelligenti da provare (anche se nessuna ha funzionato davvero) a cui non riuscivo nemmeno a pensare!
kizzx2,

9
Volevo solo notare che la complessità dell'analisi del C ++ (che è follemente complesso, sì) non ha nulla a che fare con la qualità dell'ottimizzazione. Quest'ultimo di solito avviene su stadi in cui il risultato dell'analisi viene già trasformato più volte in una rappresentazione di livello molto più basso.
Pavel Minaev,

44

Questa è una domanda vecchia ma popolare.

A questo punto, molti programmatori lavoreranno in C ++ 11. E in C ++ 11 il codice dell'OP scritto è altrettanto veloce per UseArrayo UseVector.

UseVector completed in 3.74482 seconds
UseArray completed in 3.70414 seconds

Il problema fondamentale era che mentre la tua Pixelstruttura non era inizializzata, prendeva std::vector<T>::resize( size_t, T const&=T() )un default costruito Pixele lo copiava . Il compilatore non ha notato che gli veniva chiesto di copiare dati non inizializzati, quindi ha effettivamente eseguito la copia.

In C ++ 11, std::vector<T>::resizeha due sovraccarichi. Il primo è std::vector<T>::resize(size_t), l'altro è std::vector<T>::resize(size_t, T const&). Ciò significa che quando invochi resizesenza un secondo argomento, crea semplicemente i costrutti predefiniti e il compilatore è abbastanza intelligente da rendersi conto che la costruzione predefinita non fa nulla, quindi salta il passaggio sul buffer.

(I due sovraccarichi sono stati aggiunti per gestire tipi mobili, costruibili e non copiabili: il miglioramento delle prestazioni quando si lavora su dati non inizializzati è un vantaggio).

La push_backsoluzione esegue anche il controllo di fencepost, che lo rallenta, quindi rimane più lento della mallocversione.

esempio live (ho anche sostituito il timer con chrono::high_resolution_clock).

Si noti che se si dispone di una struttura che in genere richiede l'inizializzazione, ma si desidera gestirla dopo aver aumentato il buffer, è possibile farlo con un std::vectorallocatore personalizzato . Se vuoi spostarlo in un modo più normale std::vector, credo che un uso attento allocator_traitse l'override di ciò ==potrebbe risolverlo, ma non sono sicuro.


Sarebbe anche interessante vedere come emplace_backfunziona vs push_backqui.
Daniel,

1
Non riesco a riprodurre i tuoi risultati. Compilare il tuo codice clang++ -std=c++11 -O3ha UseArray completed in 2.02e-07 secondse UseVector completed in 1.3026 seconds. Ho anche aggiunto una UseVectorEmplaceBackversione che è di ca. 2,5 volte più veloce UseVectorPushBack.
Daniel,

1
Le probabilità di @daniel sono che l'ottimizzatore ha rimosso tutto dalla versione dell'array. Sempre un rischio con i micro benchmark.
Yakk - Adam Nevraumont,

4
sì hai ragione, ho appena visto l'assemblaggio (o la sua mancanza) .. Probabilmente avresti dovuto pensarci dato la differenza di ~ 6448514x! Mi chiedo perché la versione vettoriale non possa effettuare la stessa ottimizzazione .. Lo fa se costruita con le dimensioni anziché ridimensionata.
Daniel,

34

Ad essere onesti, non è possibile confrontare un'implementazione C ++ con un'implementazione C, come definirei la tua versione malloc. malloc non crea oggetti, ma alloca solo memoria grezza. Che poi tratti quella memoria come oggetti senza chiamare il costruttore è un povero C ++ (forse non valido - lo lascerò agli avvocati linguisti).

Detto questo, cambiare semplicemente il malloc in new Pixel[dimensions*dimensions]e liberarlo delete [] pixelsnon fa molta differenza con la semplice implementazione di Pixel che hai. Ecco i risultati sulla mia scatola (E6600, 64-bit):

UseArray completed in 0.269 seconds
UseVector completed in 1.665 seconds
UseVectorPushBack completed in 7.309 seconds
The whole thing completed in 9.244 seconds

Ma con un leggero cambiamento, le tabelle girano:

Pixel.h

struct Pixel
{
    Pixel();
    Pixel(unsigned char r, unsigned char g, unsigned char b);

    unsigned char r, g, b;
};

Pixel.cc

#include "Pixel.h"

Pixel::Pixel() {}
Pixel::Pixel(unsigned char r, unsigned char g, unsigned char b) 
  : r(r), g(g), b(b) {}

main.cc

#include "Pixel.h"
[rest of test harness without class Pixel]
[UseArray now uses new/delete not malloc/free]

Compilato in questo modo:

$ g++ -O3 -c -o Pixel.o Pixel.cc
$ g++ -O3 -c -o main.o main.cc
$ g++ -o main main.o Pixel.o

otteniamo risultati molto diversi:

UseArray completed in 2.78 seconds
UseVector completed in 1.651 seconds
UseVectorPushBack completed in 7.826 seconds
The whole thing completed in 12.258 seconds

Con un costruttore non incorporato per Pixel, std :: vector ora batte un array non elaborato.

Sembrerebbe che la complessità dell'allocazione attraverso std :: vector e std: allocator sia troppo per essere ottimizzata tanto efficace quanto una semplice new Pixel[n]. Tuttavia, possiamo vedere che il problema è semplicemente l'assegnazione non l'accesso ai vettori modificando un paio di funzioni di test per creare il vettore / array una volta spostandolo all'esterno del ciclo:

void UseVector()
{
    TestTimer t("UseVector");

    int dimension = 999;
    std::vector<Pixel> pixels;
    pixels.resize(dimension * dimension);

    for(int i = 0; i < 1000; ++i)
    {
        for(int i = 0; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }
    }
}

e

void UseArray()
{
    TestTimer t("UseArray");

    int dimension = 999;
    Pixel * pixels = new Pixel[dimension * dimension];

    for(int i = 0; i < 1000; ++i)
    {
        for(int i = 0 ; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }
    }
    delete [] pixels;
}

Otteniamo questi risultati ora:

UseArray completed in 0.254 seconds
UseVector completed in 0.249 seconds
UseVectorPushBack completed in 7.298 seconds
The whole thing completed in 7.802 seconds

Ciò che possiamo imparare da questo è che std :: vector è paragonabile a un array grezzo per l'accesso, ma se è necessario creare ed eliminare il vettore / array molte volte, la creazione di un oggetto complesso richiederà più tempo rispetto alla creazione di un array semplice quando il costruttore dell'elemento non è inline. Non penso che questo sia molto sorprendente.


3
Hai ancora un costruttore incorporato: il costruttore della copia.
Ben Voigt,

26

Prova con questo:

void UseVectorCtor()
{
    TestTimer t("UseConstructor");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel> pixels(dimension * dimension, Pixel(255, 0, 0));
    }
}

Ottengo quasi esattamente le stesse prestazioni dell'array.

Il fatto vectorè che è uno strumento molto più generale di un array. E questo significa che devi considerare come lo usi. Può essere utilizzato in molti modi diversi, fornendo funzionalità che un array non ha nemmeno. E se lo usi "sbagliato" per il tuo scopo, incorri in un sacco di costi generali, ma se lo usi correttamente, di solito è fondamentalmente una struttura di dati zero-overhead. In questo caso, il problema è che hai inizializzato separatamente il vettore (facendo sì che tutti gli elementi abbiano il loro ctor predefinito chiamato) e quindi sovrascrivendo ogni elemento singolarmente con il valore corretto. Questo è molto più difficile da ottimizzare per il compilatore rispetto a quando si fa la stessa cosa con un array. Ecco perché il vettore fornisce un costruttore che ti consente di fare esattamente questo:NX.

E quando lo usi, il vettore è veloce quanto un array.

Quindi no, non hai rotto il mito della performance. Ma hai dimostrato che è vero solo se usi il vettore in modo ottimale, il che è anche un buon punto. :)

Sul lato positivo, è davvero l' uso più semplice che risulta essere il più veloce. Se si contrappone il mio frammento di codice (una sola riga) alla risposta di John Kugelman, contenente un sacco di modifiche e ottimizzazioni, che ancora non eliminano completamente la differenza di prestazioni, è abbastanza chiaro che vectordopo tutto è stato progettato in modo abbastanza intelligente. Non è necessario saltare attraverso i cerchi per ottenere una velocità pari a un array. Al contrario, devi usare la soluzione più semplice possibile.


1
Mi chiedo ancora se si tratti di un confronto equo. Se ti stai sbarazzando del ciclo interno, l'equivalente dell'array sarebbe quello di costruire un singolo oggetto Pixel e poi di blitarlo nell'intero array.
John Kugelman,

1
L'utilizzo new[]esegue le stesse costruzioni predefinite vector.resize(), ma è molto più veloce. new[]Il + ciclo interno dovrebbe avere la stessa velocità del vector.resize()+ ciclo interno, ma non lo è, è quasi il doppio della velocità.
John Kugelman,

@Giovanni: è un paragone equo. Nel codice originale, l'array è allocato con il mallocquale non inizializza o costruisce nulla, quindi è effettivamente un algoritmo single-pass proprio come il mio vectoresempio. E per quanto riguarda new[]la risposta è ovviamente che entrambi richiedono due passaggi, ma nel new[]caso il compilatore è in grado di ottimizzare quel sovraccarico aggiuntivo, cosa che non accade nel vectorcaso. Ma non vedo perché sia ​​interessante cosa succede in casi non ottimali. Se ti preoccupi delle prestazioni, non scrivi codice del genere.
jalf,

@Giovanni: commento interessante. Se volessi blittare su tutto l'array, suppongo che l'array sia di nuovo la soluzione ottimale, dal momento che non posso dire vector::resize()di darmi un pezzo di memoria contingente senza perdere tempo a chiamare costruttori inutili.
kizzx2,

@ kizzx2: sì e no. Un array è normalmente inizializzato anche in C ++. In C, useresti mallocche non esegue l'inizializzazione, ma che non funzionerà in C ++ con tipi non POD. Quindi, nel caso generale, un array C ++ sarebbe altrettanto male. Forse la domanda è: se eseguirai spesso questa fusione, non riutilizzeresti lo stesso array / vettore? E se lo fai, allora paghi il costo dei "costruttori inutili" solo una volta, all'inizio. Dopotutto, il vero successo è altrettanto veloce.
jalf,

22

Non è stato un paragone equo quando ho visto il tuo codice per la prima volta; Ho sicuramente pensato che non stavi confrontando le mele con le mele. Quindi ho pensato, chiamiamo costruttori e distruttori su tutti i test; e quindi confrontare.

const size_t dimension = 1000;

void UseArray() {
    TestTimer t("UseArray");
    for(size_t j = 0; j < dimension; ++j) {
        Pixel* pixels = new Pixel[dimension * dimension];
        for(size_t i = 0 ; i < dimension * dimension; ++i) {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = (unsigned char) (i % 255);
        }
        delete[] pixels;
    }
}

void UseVector() {
    TestTimer t("UseVector");
    for(size_t j = 0; j < dimension; ++j) {
        std::vector<Pixel> pixels(dimension * dimension);
        for(size_t i = 0; i < dimension * dimension; ++i) {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = (unsigned char) (i % 255);
        }
    }
}

int main() {
    TestTimer t1("The whole thing");

    UseArray();
    UseVector();

    return 0;
}

I miei pensieri erano che con questa configurazione avrebbero dovuto essere esattamente gli stessi. Si scopre che mi sbagliavo.

UseArray completed in 3.06 seconds
UseVector completed in 4.087 seconds
The whole thing completed in 10.14 seconds

Quindi perché si è verificata questa perdita di prestazioni del 30%? L'STL ha tutto nelle intestazioni, quindi il compilatore avrebbe dovuto capire tutto ciò che era richiesto.

Il mio pensiero era che è in che modo il ciclo inizializza tutti i valori al costruttore predefinito. Quindi ho eseguito un test:

class Tester {
public:
    static int count;
    static int count2;
    Tester() { count++; }
    Tester(const Tester&) { count2++; }
};
int Tester::count = 0;
int Tester::count2 = 0;

int main() {
    std::vector<Tester> myvec(300);
    printf("Default Constructed: %i\nCopy Constructed: %i\n", Tester::count, Tester::count2);

    return 0;
}

I risultati furono come sospettavo:

Default Constructed: 1
Copy Constructed: 300

Questa è chiaramente la fonte del rallentamento, il fatto che il vettore usi il costruttore della copia per inizializzare gli elementi da un oggetto costruito di default.

Ciò significa che durante la costruzione del vettore sta avvenendo il seguente ordine di pseudo-operazione:

Pixel pixel;
for (auto i = 0; i < N; ++i) vector[i] = pixel;

Che, a causa del costruttore di copie implicite creato dal compilatore, viene espanso come segue:

Pixel pixel;
for (auto i = 0; i < N; ++i) {
    vector[i].r = pixel.r;
    vector[i].g = pixel.g;
    vector[i].b = pixel.b;
}

Quindi il valore di default Pixelrimane non-inizializzato, mentre il resto sono inizializzato con il valore di default Pixel's non-inizializzato valori.

Rispetto alla situazione alternativa con New[]/ Delete[]:

int main() {
    Tester* myvec = new Tester[300];

    printf("Default Constructed: %i\nCopy Constructed:%i\n", Tester::count, Tester::count2);

    delete[] myvec;

    return 0;
}

Default Constructed: 300
Copy Constructed: 0

Sono tutti lasciati ai loro valori non inizializzati e senza la doppia iterazione sulla sequenza.

Grazie a queste informazioni, come possiamo testarle? Proviamo a sovrascrivere il costruttore di copie implicite.

Pixel(const Pixel&) {}

E i risultati?

UseArray completed in 2.617 seconds
UseVector completed in 2.682 seconds
The whole thing completed in 5.301 seconds

Quindi, in sintesi, se stai creando centinaia di vettori molto spesso: ripensa al tuo algoritmo .

In ogni caso, l' implementazione STL non è più lenta per qualche motivo sconosciuto, fa esattamente quello che chiedi; sperando che tu lo sappia meglio.


3
A giudicare dal divertimento che noi (io e te e altre persone intelligenti qui) abbiamo avuto, la "speranza" dell'attuazione di STL è davvero piuttosto impegnativa: P Fondamentalmente, possiamo esagerare e concludere che spera di aver letto e analizzato tutta la sua fonte codice. Comunque: P
kizzx2

1
Stupendo! In VS 2013 questo ha reso il vettore più veloce degli array. Anche se sembra che per i sistemi con prestazioni critiche sia necessario testare molto l'STL per poterlo utilizzare in modo efficace.
Rozina,

7

Prova a disabilitare gli iteratori controllati e crearli in modalità di rilascio. Non dovresti vedere molta differenza nelle prestazioni.


1
Provato #define _SECURE_SCL 0. Ciò ha reso UseVectorda qualche parte circa 4 secondi (simile al gccsotto) ma è comunque due volte più lento.
kizzx2,

Questa è quasi sicuramente la causa. Microsoft ci impegna cortesemente a eseguire il debug iteratore per impostazione predefinita sia per il debug sia per il rilascio. Abbiamo scoperto che questa è la causa principale di un forte rallentamento dopo l'aggiornamento dal 2003 al 2008. Sicuramente uno dei gotcha più dannosi di Visual Studio.
Doug T.

2
@ kizzx2 c'è un'altra macro da disabilitare: HAS_ITERATOR_DEBUGGING o qualcosa del genere.
Doug T.

Come mostrano @Martin e le mie risposte, gcc mostra lo stesso modello, anche con l'ottimizzazione a -O3.
John Kugelman,

1
@Doug: Guardando il doc, penso che _HAS_ITERATOR_DEBUGGINGè disattivata in build di rilascio: msdn.microsoft.com/en-us/library/aa985939(VS.80).aspx
kizzx2

4

L'STL di GNU (e altri), dato vector<T>(n), predefinito costruisce un oggetto prototipo T()- il compilatore ottimizzerà il costruttore vuoto - ma poi una copia di qualsiasi spazzatura trovata negli indirizzi di memoria ora riservati per l'oggetto viene presa dagli STL __uninitialized_fill_n_aux, che esegue il loop popolando le copie di quell'oggetto come valori predefiniti nel vettore. Quindi, "my" STL non sta costruendo in loop, ma costruendo quindi loop / copia. È contro intuitivo, ma avrei dovuto ricordare come ho commentato una recente domanda di StackOver su questo punto: la costruzione / copia può essere più efficiente per gli oggetti contati di riferimento ecc.

Così:

vector<T> x(n);

o

vector<T> x;
x.resize(n);

è - su molte implementazioni STL - qualcosa del tipo:

T temp;
for (int i = 0; i < n; ++i)
    x[i] = temp;

Il problema è che l'attuale generazione di ottimizzatori del compilatore non sembra funzionare dall'intuizione che temp è immondizia non inizializzata e non riesce a ottimizzare il ciclo e le invocazioni predefinite del costruttore di copie. Si potrebbe argomentare in modo credibile che i compilatori non dovrebbero assolutamente ottimizzarlo, poiché un programmatore che scrive quanto sopra ha una ragionevole aspettativa che tutti gli oggetti saranno identici dopo il ciclo, anche se spazzatura (soliti avvertimenti su 'identico' / operatore == vs memcmp / operator = etc si applicano). Non ci si può aspettare che il compilatore abbia ulteriori informazioni sul contesto più ampio di std :: vector <> o sull'uso successivo dei dati che suggerirebbero che questa ottimizzazione sia sicura.

Ciò può essere contrastato con l'implementazione diretta più ovvia:

for (int i = 0; i < n; ++i)
    x[i] = T();

Che possiamo aspettarci che un compilatore ottimizzi.

Per essere un po 'più esplicito sulla giustificazione di questo aspetto del comportamento del vettore, considera:

std::vector<big_reference_counted_object> x(10000);

Chiaramente è una grande differenza se facciamo 10000 oggetti indipendenti contro 10000 che fanno riferimento agli stessi dati. C'è un'argomentazione ragionevole secondo cui il vantaggio di proteggere gli utenti casuali di C ++ dal fare accidentalmente qualcosa di così costoso supera il costo molto ridotto del mondo reale della costruzione di copie difficile da ottimizzare.

RISPOSTA ORIGINALE (per riferimento / dare un senso ai commenti): nessuna possibilità. il vettore è veloce come un array, almeno se riservi lo spazio in modo sensibile. ...


6
Non posso davvero giustificare che questa risposta sia in qualche modo leggermente utile a nessuno. Spero di poter votare due volte.
kizzx2,

-1, arriva il mio supporto su kizzx2. il vettore non sarà mai veloce come l'array a causa della funzionalità aggiuntiva che fornisce, regola dell'universo, tutto ha un prezzo!
YeenFei,

Ti stai perdendo, Tony ... è un esempio di punto di riferimento artificiale, ma dimostra ciò che pretende.
Potatoswatter l'

Le rose sono verdi, le viole sono arancioni, i voti negativi sono amari, ma la risposta li supplica.
Pavel Minaev,

3

La risposta di Martin York mi dà fastidio perché sembra un tentativo di eliminare il problema dell'inizializzazione sotto il tappeto. Ma ha ragione a identificare la costruzione predefinita ridondante come fonte di problemi di prestazioni.

[EDIT: la risposta di Martin non suggerisce più di cambiare il costruttore predefinito.]

Per il problema immediato a portata di mano, puoi invece chiamare la versione a 2 parametri del vector<Pixel>ctor:

std::vector<Pixel> pixels(dimension * dimension, Pixel(255, 0, 0));

Funziona se si desidera inizializzare con un valore costante, che è un caso comune. Ma il problema più generale è: come si può inizializzare in modo efficiente con qualcosa di più complicato di un valore costante?

Per questo puoi usare a back_insert_iterator, che è un adattatore iteratore. Ecco un esempio con un vettore di ints, anche se l'idea generale funziona altrettanto bene per Pixels:

#include <iterator>
// Simple functor return a list of squares: 1, 4, 9, 16...
struct squares {
    squares() { i = 0; }
    int operator()() const { ++i; return i * i; }

private:
    int i;
};

...

std::vector<int> v;
v.reserve(someSize);     // To make insertions efficient
std::generate_n(std::back_inserter(v), someSize, squares());

In alternativa puoi usare copy()o transform()invece di generate_n().

Il rovescio della medaglia è che la logica per costruire i valori iniziali deve essere spostata in una classe separata, il che è meno conveniente che averlo sul posto (anche se lambda in C ++ 1x lo rendono molto più bello). Inoltre mi aspetto che questo non sarà ancora veloce come una malloc()versione non-STL basata su, ma mi aspetto che sarà vicino, dal momento che fa solo una costruzione per ogni elemento.


2

Quelli vettoriali chiamano anche i costruttori Pixel.

Ognuno sta causando quasi un milione di corse di ctor che stai programmando.

modifica: poi c'è il loop esterno 1 ... 1000, quindi fai chiamare un miliardo di ctor!

modifica 2: sarebbe interessante vedere lo smontaggio per il caso UseArray. Un ottimizzatore potrebbe ottimizzare il tutto, dal momento che non ha alcun effetto se non la masterizzazione della CPU.


Hai ragione, ma la domanda è: come possono essere disattivate queste inutili chiamate ctor? È facile per l'approccio non STL, ma difficile / brutto per il modo STL.
j_random_hacker

1

Ecco come funziona il push_backmetodo nel vettore:

  1. Il vettore alloca X quantità di spazio quando viene inizializzato.
  2. Come indicato di seguito, controlla se vi è spazio nell'array sottostante corrente per l'elemento.
  3. Crea una copia dell'elemento nella chiamata push_back.

Dopo aver chiamato push_backX elementi:

  1. Il vettore rialloca la quantità di spazio kX in un secondo array.
  2. Copia le voci del primo array sul secondo.
  3. Elimina il primo array.
  4. Ora usa il secondo array come memoria fino a quando non raggiunge le voci kX.

Ripetere. Se non sei reservingspazio, sarà sicuramente più lento. Inoltre, se è costoso copiare l'oggetto, allora "push_back" come quello ti divorerà vivo.

Per quanto riguarda il vectorversus array, dovrò essere d'accordo con le altre persone. Esegui il rilascio, attiva le ottimizzazioni e inserisci alcune altre bandiere in modo che le persone amichevoli di Microsoft non lo facciano # @% $ ^.

Un'altra cosa, se non hai bisogno di ridimensionare, usa Boost.Array.


Capisco che alla gente non piace leggere un sacco di codice quando viene pubblicato alla lettera. Ma l'ho usato reservecome avrei dovuto.
kizzx2,

Scusa se l'ho perso. Nient'altro che ho messo lì è stato utile?
Wheaties

push_backha ammortizzato il tempo costante. Sembra che tu stia descrivendo un processo O (N). (I passaggi 1 e 3 sembrano completamente fuori posto.) Ciò che rende push_backlento per OP è il controllo dell'intervallo per vedere se deve avvenire la riallocazione, l'aggiornamento dei puntatori, il controllo rispetto al posizionamento NULL all'interno newe altre piccole cose che normalmente vengono annegate da il lavoro effettivo del programma.
Potatoswatter l'

Sarà più lento anche reserveperché deve ancora fare quel controllo (se deve riallocare) su ogni push_back.
Pavel Minaev,

Tutti i punti positivi. Quello che sto descrivendo suona come un processo O (N) ma non lo è, beh non del tutto. La maggior parte delle persone che conosco non capisce come si vectoresibisce è la funzionalità di ridimensionamento, è solo "magico". Vorrei chiarire un po 'di più.
Wheaties

1

Alcuni dati del profiler (il pixel è allineato a 32 bit):

g++ -msse3 -O3 -ftree-vectorize -g test.cpp -DNDEBUG && ./a.out
UseVector completed in 3.123 seconds
UseArray completed in 1.847 seconds
UseVectorPushBack completed in 9.186 seconds
The whole thing completed in 14.159 seconds

blah

andrey@nv:~$ opannotate --source libcchem/src/a.out  | grep "Total samples for file" -A3
Overflow stats not available
 * Total samples for file : "/usr/include/c++/4.4/ext/new_allocator.h"
 *
 * 141008 52.5367
 */
--
 * Total samples for file : "/home/andrey/libcchem/src/test.cpp"
 *
 *  61556 22.9345
 */
--
 * Total samples for file : "/usr/include/c++/4.4/bits/stl_vector.h"
 *
 *  41956 15.6320
 */
--
 * Total samples for file : "/usr/include/c++/4.4/bits/stl_uninitialized.h"
 *
 *  20956  7.8078
 */
--
 * Total samples for file : "/usr/include/c++/4.4/bits/stl_construct.h"
 *
 *   2923  1.0891
 */

In allocator:

               :      // _GLIBCXX_RESOLVE_LIB_DEFECTS
               :      // 402. wrong new expression in [some_] allocator::construct
               :      void
               :      construct(pointer __p, const _Tp& __val)
141008 52.5367 :      { ::new((void *)__p) _Tp(__val); }

vector:

               :void UseVector()
               :{ /* UseVector() total:  60121 22.3999 */
...
               :
               :
 10790  4.0201 :        for (int i = 0; i < dimension * dimension; ++i) {
               :
   495  0.1844 :            pixels[i].r = 255;
               :
 12618  4.7012 :            pixels[i].g = 0;
               :
  2253  0.8394 :            pixels[i].b = 0;
               :
               :        }

Vettore

               :void UseArray()
               :{ /* UseArray() total:  35191 13.1114 */
               :
...
               :
   136  0.0507 :        for (int i = 0; i < dimension * dimension; ++i) {
               :
  9897  3.6874 :            pixels[i].r = 255;
               :
  3511  1.3081 :            pixels[i].g = 0;
               :
 21647  8.0652 :            pixels[i].b = 0;

Gran parte del sovraccarico è nel costruttore della copia. Per esempio,

    std::vector < Pixel > pixels;//(dimension * dimension, Pixel());

    pixels.reserve(dimension * dimension);

    for (int i = 0; i < dimension * dimension; ++i) {

        pixels[i].r = 255;

        pixels[i].g = 0;

        pixels[i].b = 0;
    }

Ha le stesse prestazioni di un array.


2
Sfortunatamente, dopo la "soluzione" che hai dato, pixels.size()sarà rotta.
kizzx2,

1
questo è sbagliato, non è possibile chiamare la riserva e quindi utilizzare gli elementi, è comunque necessario utilizzare push_back per aggiungere elementi
paulm

1

Il mio laptop è Lenova G770 (4 GB di RAM).

Il sistema operativo è Windows 7 a 64 bit (quello con laptop)

Il compilatore è MinGW 4.6.1.

L'IDE è Code :: Blocks .

Metto alla prova i codici sorgente del primo post.

I risultati

Ottimizzazione O2

Array Use completato in 2.841 secondi

UseVector completato in 2.548 secondi

UseVectorPushBack completato in 11,95 secondi

Il tutto completato in 17.342 secondi

pausa del sistema

Ottimizzazione O3

Array Use completato in 1.452 secondi

UseVector completato in 2.514 secondi

UseVectorPushBack completato in 12.967 secondi

Il tutto completato in 16.937 secondi

Sembra che le prestazioni del vettore siano peggiori con l'ottimizzazione O3.

Se cambi il loop in

    pixels[i].r = i;
    pixels[i].g = i;
    pixels[i].b = i;

La velocità di array e vettore in O2 e O3 è quasi la stessa.


Anche io cambio malloc in nuovo, nel primo caso di test in O3, le prestazioni del vettore sono ancora più lente dell'array, ma quando cambi il valore di assegnazione da (255, 0, 0) a (i, i, i), le prestazioni di vettore e array sono quasi gli stessi in O2 e O3, è piuttosto strano
StereoMatching

Scusate, ho dimenticato di cambiare gratis per cancellare.Dopo aver cambiato libero per cancellare, le prestazioni in O3 di vettore e matrice sono le stesse ora, sembra che l'allocatore sia il motivo principale?
StereoMatching

1

Un benchmark migliore (penso ...), il compilatore a causa di ottimizzazioni può cambiare codice, perché i risultati di vettori / array allocati non vengono utilizzati da nessuna parte. risultati:

$ g++ test.cpp -o test -O3 -march=native
$ ./test 
UseArray inner completed in 0.652 seconds
UseArray completed in 0.773 seconds
UseVector inner completed in 0.638 seconds
UseVector completed in 0.757 seconds
UseVectorPushBack inner completed in 6.732 seconds
UseVectorPush completed in 6.856 seconds
The whole thing completed in 8.387 seconds

Compiler:

gcc version 6.2.0 20161019 (Debian 6.2.0-9)

PROCESSORE:

model name  : Intel(R) Core(TM) i7-3630QM CPU @ 2.40GHz

E il codice:

#include <cstdlib>
#include <vector>

#include <iostream>
#include <string>

#include <boost/date_time/posix_time/ptime.hpp>
#include <boost/date_time/microsec_time_clock.hpp>

class TestTimer
{
    public:
        TestTimer(const std::string & name) : name(name),
            start(boost::date_time::microsec_clock<boost::posix_time::ptime>::local_time())
        {
        }

        ~TestTimer()
        {
            using namespace std;
            using namespace boost;

            posix_time::ptime now(date_time::microsec_clock<posix_time::ptime>::local_time());
            posix_time::time_duration d = now - start;

            cout << name << " completed in " << d.total_milliseconds() / 1000.0 <<
                " seconds" << endl;
        }

    private:
        std::string name;
        boost::posix_time::ptime start;
};

struct Pixel
{
    Pixel()
    {
    }

    Pixel(unsigned char r, unsigned char g, unsigned char b) : r(r), g(g), b(b)
    {
    }

    unsigned char r, g, b;
};

void UseVector(std::vector<std::vector<Pixel> >& results)
{
    TestTimer t("UseVector inner");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel>& pixels = results.at(i);
        pixels.resize(dimension * dimension);

        for(int i = 0; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }
    }
}

void UseVectorPushBack(std::vector<std::vector<Pixel> >& results)
{
    TestTimer t("UseVectorPushBack inner");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel>& pixels = results.at(i);
            pixels.reserve(dimension * dimension);

        for(int i = 0; i < dimension * dimension; ++i)
            pixels.push_back(Pixel(255, 0, 0));
    }
}

void UseArray(Pixel** results)
{
    TestTimer t("UseArray inner");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        Pixel * pixels = (Pixel *)malloc(sizeof(Pixel) * dimension * dimension);

        results[i] = pixels;

        for(int i = 0 ; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }

        // free(pixels);
    }
}

void UseArray()
{
    TestTimer t("UseArray");
    Pixel** array = (Pixel**)malloc(sizeof(Pixel*)* 1000);
    UseArray(array);
    for(int i=0;i<1000;++i)
        free(array[i]);
    free(array);
}

void UseVector()
{
    TestTimer t("UseVector");
    {
        std::vector<std::vector<Pixel> > vector(1000, std::vector<Pixel>());
        UseVector(vector);
    }
}

void UseVectorPushBack()
{
    TestTimer t("UseVectorPush");
    {
        std::vector<std::vector<Pixel> > vector(1000, std::vector<Pixel>());
        UseVectorPushBack(vector);
    }
}


int main()
{
    TestTimer t1("The whole thing");

    UseArray();
    UseVector();
    UseVectorPushBack();

    return 0;
}

1

Ho fatto alcuni test approfonditi che volevo da un po 'di tempo. Potrebbe anche condividere questo.

Questa è la mia macchina dual boot i7-3770, 16 GB di RAM, x86_64, su Windows 8.1 e su Ubuntu 16.04. Ulteriori informazioni e conclusioni, osservazioni di seguito. Testato sia su MSVS 2017 che su g ++ (sia su Windows che su Linux).

Programma di test

#include <iostream>
#include <chrono>
//#include <algorithm>
#include <array>
#include <locale>
#include <vector>
#include <queue>
#include <deque>

// Note: total size of array must not exceed 0x7fffffff B = 2,147,483,647B
//  which means that largest int array size is 536,870,911
// Also image size cannot be larger than 80,000,000B
constexpr int long g_size = 100000;
int g_A[g_size];


int main()
{
    std::locale loc("");
    std::cout.imbue(loc);
    constexpr int long size = 100000;  // largest array stack size

    // stack allocated c array
    std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
    int A[size];
    for (int i = 0; i < size; i++)
        A[i] = i;

    auto duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "c-style stack array duration=" << duration / 1000.0 << "ms\n";
    std::cout << "c-style stack array size=" << sizeof(A) << "B\n\n";

    // global stack c array
    start = std::chrono::steady_clock::now();
    for (int i = 0; i < g_size; i++)
        g_A[i] = i;

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "global c-style stack array duration=" << duration / 1000.0 << "ms\n";
    std::cout << "global c-style stack array size=" << sizeof(g_A) << "B\n\n";

    // raw c array heap array
    start = std::chrono::steady_clock::now();
    int* AA = new int[size];    // bad_alloc() if it goes higher than 1,000,000,000
    for (int i = 0; i < size; i++)
        AA[i] = i;

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "c-style heap array duration=" << duration / 1000.0 << "ms\n";
    std::cout << "c-style heap array size=" << sizeof(AA) << "B\n\n";
    delete[] AA;

    // std::array<>
    start = std::chrono::steady_clock::now();
    std::array<int, size> AAA;
    for (int i = 0; i < size; i++)
        AAA[i] = i;
    //std::sort(AAA.begin(), AAA.end());

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "std::array duration=" << duration / 1000.0 << "ms\n";
    std::cout << "std::array size=" << sizeof(AAA) << "B\n\n";

    // std::vector<>
    start = std::chrono::steady_clock::now();
    std::vector<int> v;
    for (int i = 0; i < size; i++)
        v.push_back(i);
    //std::sort(v.begin(), v.end());

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "std::vector duration=" << duration / 1000.0 << "ms\n";
    std::cout << "std::vector size=" << v.size() * sizeof(v.back()) << "B\n\n";

    // std::deque<>
    start = std::chrono::steady_clock::now();
    std::deque<int> dq;
    for (int i = 0; i < size; i++)
        dq.push_back(i);
    //std::sort(dq.begin(), dq.end());

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "std::deque duration=" << duration / 1000.0 << "ms\n";
    std::cout << "std::deque size=" << dq.size() * sizeof(dq.back()) << "B\n\n";

    // std::queue<>
    start = std::chrono::steady_clock::now();
    std::queue<int> q;
    for (int i = 0; i < size; i++)
        q.push(i);

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "std::queue duration=" << duration / 1000.0 << "ms\n";
    std::cout << "std::queue size=" << q.size() * sizeof(q.front()) << "B\n\n";
}

risultati

//////////////////////////////////////////////////////////////////////////////////////////
// with MSVS 2017:
// >> cl /std:c++14 /Wall -O2 array_bench.cpp
//
// c-style stack array duration=0.15ms
// c-style stack array size=400,000B
//
// global c-style stack array duration=0.130ms
// global c-style stack array size=400,000B
//
// c-style heap array duration=0.90ms
// c-style heap array size=4B
//
// std::array duration=0.20ms
// std::array size=400,000B
//
// std::vector duration=0.544ms
// std::vector size=400,000B
//
// std::deque duration=1.375ms
// std::deque size=400,000B
//
// std::queue duration=1.491ms
// std::queue size=400,000B
//
//////////////////////////////////////////////////////////////////////////////////////////
//
// with g++ version:
//      - (tdm64-1) 5.1.0 on Windows
//      - (Ubuntu 5.4.0-6ubuntu1~16.04.10) 5.4.0 20160609 on Ubuntu 16.04
// >> g++ -std=c++14 -Wall -march=native -O2 array_bench.cpp -o array_bench
//
// c-style stack array duration=0ms
// c-style stack array size=400,000B
//
// global c-style stack array duration=0.124ms
// global c-style stack array size=400,000B
//
// c-style heap array duration=0.648ms
// c-style heap array size=8B
//
// std::array duration=1ms
// std::array size=400,000B
//
// std::vector duration=0.402ms
// std::vector size=400,000B
//
// std::deque duration=0.234ms
// std::deque size=400,000B
//
// std::queue duration=0.304ms
// std::queue size=400,000
//
//////////////////////////////////////////////////////////////////////////////////////////

Appunti

  • Assemblato da una media di 10 corse.
  • Inizialmente ho eseguito anche i test std::sort()(puoi vederlo commentato) ma li ho rimossi in seguito perché non c'erano differenze relative significative.

Le mie conclusioni e osservazioni

  • notate come l'array c-style globale impiega quasi tutto il tempo dell'array c-style heap
  • Tra tutti i test ho notato una notevole stabilità nelle std::arrayvariazioni temporali tra le esecuzioni consecutive, mentre altre in particolare le strutture std :: data sono variate notevolmente
  • L'ottimizzazione O3 non ha mostrato differenze di tempo degne di nota
  • Rimuovere l'ottimizzazione su Windows cl (no -O2) e su g ++ (Win / Linux no -O2, no -march = native) aumenta i tempi SIGNIFICATIVAMENTE. Soprattutto per le strutture std :: data. Complessivamente tempi maggiori su MSVS rispetto a g ++, ma std::arraye matrici di tipo c più veloci su Windows senza ottimizzazione
  • g ++ produce codice più veloce del compilatore di Microsoft (apparentemente funziona più velocemente anche su Windows).

Verdetto

Naturalmente questo è il codice per una build ottimizzata. E dato che la domanda riguardava std::vectorallora sì, lo è! Molto! più lento degli array semplici (ottimizzato / non ottimizzato). Ma quando stai facendo un benchmark, naturalmente vuoi produrre un codice ottimizzato.

Per me la star dello spettacolo è stata std::array.


0

Con le giuste opzioni, i vettori e le matrici possono generare asm identici . In questi casi, hanno ovviamente la stessa velocità, perché ottieni lo stesso file eseguibile in entrambi i modi.


1
In questo caso, non sembrano generare lo stesso assembly. In particolare, non sembra esserci alcun modo per sopprimere la chiamata ai costruttori utilizzando i vettori. Puoi fare riferimento alle risposte qui per quel problema (è una lettura lunga ma spiega perché c'è una differenza di prestazioni in casi diversi dal test case semplice nel link che hai provocato.) (In realtà, sembra esserci un modo - - scrivere un allocatore STL personalizzato, come suggerito. Personalmente, trovo inutilmente più lavoro che usare malloc)
kizzx2

1
@ kizzx2: che devi fare di tutto per usare oggetti non costruiti è una buona cosa, perché è un errore del 99% (potrei essere gravemente sottovalutato) del tempo. Ho letto le altre risposte e mi rendo conto di non affrontare la tua situazione specifica (nessuna necessità, le altre risposte sono corrette), ma volevo comunque fornirti questo esempio di come i vettori e gli array possono comportarsi esattamente allo stesso modo.

@Roger: fantastico! Grazie per il link
kizzx2,

0

A proposito, il rallentamento del tuo vedere nelle classi usando vector si verifica anche con tipi standard come int. Ecco un codice multithread:

#include <iostream>
#include <cstdio>
#include <map>
#include <string>
#include <typeinfo>
#include <vector>
#include <pthread.h>
#include <sstream>
#include <fstream>
using namespace std;

//pthread_mutex_t map_mutex=PTHREAD_MUTEX_INITIALIZER;

long long num=500000000;
int procs=1;

struct iterate
{
    int id;
    int num;
    void * member;
    iterate(int a, int b, void *c) : id(a), num(b), member(c) {}
};

//fill out viterate and piterate
void * viterate(void * input)
{
    printf("am in viterate\n");
    iterate * info=static_cast<iterate *> (input);
    // reproduce member type
    vector<int> test= *static_cast<vector<int>*> (info->member);
    for (int i=info->id; i<test.size(); i+=info->num)
    {
        //printf("am in viterate loop\n");
        test[i];
    }
    pthread_exit(NULL);
}

void * piterate(void * input)
{
    printf("am in piterate\n");
    iterate * info=static_cast<iterate *> (input);;
    int * test=static_cast<int *> (info->member);
    for (int i=info->id; i<num; i+=info->num) {
        //printf("am in piterate loop\n");
        test[i];
    }
    pthread_exit(NULL);
}

int main()
{
    cout<<"producing vector of size "<<num<<endl;
    vector<int> vtest(num);
    cout<<"produced  a vector of size "<<vtest.size()<<endl;
    pthread_t thread[procs];

    iterate** it=new iterate*[procs];
    int ans;
    void *status;

    cout<<"begining to thread through the vector\n";
    for (int i=0; i<procs; i++) {
        it[i]=new iterate(i, procs, (void *) &vtest);
    //  ans=pthread_create(&thread[i],NULL,viterate, (void *) it[i]);
    }
    for (int i=0; i<procs; i++) {
        pthread_join(thread[i], &status);
    }
    cout<<"end of threading through the vector";
    //reuse the iterate structures

    cout<<"producing a pointer with size "<<num<<endl;
    int * pint=new int[num];
    cout<<"produced a pointer with size "<<num<<endl;

    cout<<"begining to thread through the pointer\n";
    for (int i=0; i<procs; i++) {
        it[i]->member=&pint;
        ans=pthread_create(&thread[i], NULL, piterate, (void*) it[i]);
    }
    for (int i=0; i<procs; i++) {
        pthread_join(thread[i], &status);
    }
    cout<<"end of threading through the pointer\n";

    //delete structure array for iterate
    for (int i=0; i<procs; i++) {
        delete it[i];
    }
    delete [] it;

    //delete pointer
    delete [] pint;

    cout<<"end of the program"<<endl;
    return 0;
}

Il comportamento del codice mostra che l'istanza del vettore è la parte più lunga del codice. Una volta superato quel collo di bottiglia. Il resto del codice è estremamente veloce. Questo è vero indipendentemente da quanti thread stai eseguendo.

A proposito, ignora il numero assolutamente folle di inclusioni. Ho usato questo codice per testare le cose per un progetto, quindi il numero di inclusioni continua a crescere.


0

Voglio solo menzionare che vector (e smart_ptr) è solo un sottile strato aggiunto sopra array grezzi (e puntatori grezzi). E in realtà il tempo di accesso di un vettore nella memoria continua è più veloce dell'array. Il codice seguente mostra il risultato di inizializzazione e accesso al vettore e alla matrice.

#include <boost/date_time/posix_time/posix_time.hpp>
#include <iostream>
#include <vector>
#define SIZE 20000
int main() {
    srand (time(NULL));
    vector<vector<int>> vector2d;
    vector2d.reserve(SIZE);
    int index(0);
    boost::posix_time::ptime start_total = boost::posix_time::microsec_clock::local_time();
    //  timer start - build + access
    for (int i = 0; i < SIZE; i++) {
        vector2d.push_back(vector<int>(SIZE));
    }
    boost::posix_time::ptime start_access = boost::posix_time::microsec_clock::local_time();
    //  timer start - access
    for (int i = 0; i < SIZE; i++) {
        index = rand()%SIZE;
        for (int j = 0; j < SIZE; j++) {

            vector2d[index][index]++;
        }
    }
    boost::posix_time::ptime end = boost::posix_time::microsec_clock::local_time();
    boost::posix_time::time_duration msdiff = end - start_total;
    cout << "Vector total time: " << msdiff.total_milliseconds() << "milliseconds.\n";
    msdiff = end - start_acess;
    cout << "Vector access time: " << msdiff.total_milliseconds() << "milliseconds.\n"; 


    int index(0);
    int** raw2d = nullptr;
    raw2d = new int*[SIZE];
    start_total = boost::posix_time::microsec_clock::local_time();
    //  timer start - build + access
    for (int i = 0; i < SIZE; i++) {
        raw2d[i] = new int[SIZE];
    }
    start_access = boost::posix_time::microsec_clock::local_time();
    //  timer start - access
    for (int i = 0; i < SIZE; i++) {
        index = rand()%SIZE;
        for (int j = 0; j < SIZE; j++) {

            raw2d[index][index]++;
        }
    }
    end = boost::posix_time::microsec_clock::local_time();
    msdiff = end - start_total;
    cout << "Array total time: " << msdiff.total_milliseconds() << "milliseconds.\n";
    msdiff = end - start_acess;
    cout << "Array access time: " << msdiff.total_milliseconds() << "milliseconds.\n"; 
    for (int i = 0; i < SIZE; i++) {
        delete [] raw2d[i];
    }
    return 0;
}

L'output è:

    Vector total time: 925milliseconds.
    Vector access time: 4milliseconds.
    Array total time: 30milliseconds.
    Array access time: 21milliseconds.

Quindi la velocità sarà quasi la stessa se la usi correttamente. (come altri hanno menzionato usando reserve () o resize ()).


0

Bene, perché vector :: resize () fa molta più elaborazione della semplice allocazione di memoria (di malloc).

Prova a mettere un punto di interruzione nel costruttore della copia (definiscilo in modo da poter interrompere il punto di interruzione!) E il tempo di elaborazione aumenta.


0

Devo dire che non sono un esperto di C ++. Ma per aggiungere alcuni risultati degli esperimenti:

compilare: gcc-6.2.0 / bin / g ++ -O3 -std = c ++ 14 vector.cpp

macchina:

Intel(R) Xeon(R) CPU E5-2690 v2 @ 3.00GHz 

OS:

2.6.32-642.13.1.el6.x86_64

Produzione:

UseArray completed in 0.167821 seconds
UseVector completed in 0.134402 seconds
UseConstructor completed in 0.134806 seconds
UseFillConstructor completed in 1.00279 seconds
UseVectorPushBack completed in 6.6887 seconds
The whole thing completed in 8.12888 seconds

Qui l'unica cosa che mi sento strana è la performance di "UseFillConstructor" rispetto a "UseConstructor".

Il codice:

void UseConstructor()
{
    TestTimer t("UseConstructor");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel> pixels(dimension*dimension);
        for(int i = 0; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }
    }
}


void UseFillConstructor()
{
    TestTimer t("UseFillConstructor");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel> pixels(dimension*dimension, Pixel(255,0,0));
    }
}

Quindi il "valore" aggiuntivo fornito rallenta parecchio le prestazioni, cosa che penso sia dovuta a più chiamate al costruttore della copia. Ma...

Compilare:

gcc-6.2.0/bin/g++ -std=c++14 -O vector.cpp

Produzione:

UseArray completed in 1.02464 seconds
UseVector completed in 1.31056 seconds
UseConstructor completed in 1.47413 seconds
UseFillConstructor completed in 1.01555 seconds
UseVectorPushBack completed in 6.9597 seconds
The whole thing completed in 11.7851 seconds

Quindi, in questo caso, l'ottimizzazione di gcc è molto importante ma non può aiutarti molto quando un valore viene fornito come predefinito. Questo, in realtà, è contro le mie lezioni. Speriamo che aiuti il ​​nuovo programmatore a scegliere il formato di inizializzazione vettoriale.


0

Sembra dipendere dai flag del compilatore. Ecco un codice di riferimento:

#include <chrono>
#include <cmath>
#include <ctime>
#include <iostream>
#include <vector>


int main(){

    int size = 1000000; // reduce this number in case your program crashes
    int L = 10;

    std::cout << "size=" << size << " L=" << L << std::endl;
    {
        srand( time(0) );
        double * data = new double[size];
        double result = 0.;
        std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
        for( int l = 0; l < L; l++ ) {
            for( int i = 0; i < size; i++ ) data[i] = rand() % 100;
            for( int i = 0; i < size; i++ ) result += data[i] * data[i];
        }
        std::chrono::steady_clock::time_point end   = std::chrono::steady_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
        std::cout << "Calculation result is " << sqrt(result) << "\n";
        std::cout << "Duration of C style heap array:    " << duration << "ms\n";
        delete data;
    }

    {
        srand( 1 + time(0) );
        double data[size]; // technically, non-compliant with C++ standard.
        double result = 0.;
        std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
        for( int l = 0; l < L; l++ ) {
            for( int i = 0; i < size; i++ ) data[i] = rand() % 100;
            for( int i = 0; i < size; i++ ) result += data[i] * data[i];
        }
        std::chrono::steady_clock::time_point end   = std::chrono::steady_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
        std::cout << "Calculation result is " << sqrt(result) << "\n";
        std::cout << "Duration of C99 style stack array: " << duration << "ms\n";
    }

    {
        srand( 2 + time(0) );
        std::vector<double> data( size );
        double result = 0.;
        std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
        for( int l = 0; l < L; l++ ) {
            for( int i = 0; i < size; i++ ) data[i] = rand() % 100;
            for( int i = 0; i < size; i++ ) result += data[i] * data[i];
        }
        std::chrono::steady_clock::time_point end   = std::chrono::steady_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
        std::cout << "Calculation result is " << sqrt(result) << "\n";
        std::cout << "Duration of std::vector array:     " << duration << "ms\n";
    }

    return 0;
}

Diversi flag di ottimizzazione danno risposte diverse:

$ g++ -O0 benchmark.cpp 
$ ./a.out 
size=1000000 L=10
Calculation result is 181182
Duration of C style heap array:    118441ms
Calculation result is 181240
Duration of C99 style stack array: 104920ms
Calculation result is 181210
Duration of std::vector array:     124477ms
$g++ -O3 benchmark.cpp
$ ./a.out 
size=1000000 L=10
Calculation result is 181213
Duration of C style heap array:    107803ms
Calculation result is 181198
Duration of C99 style stack array: 87247ms
Calculation result is 181204
Duration of std::vector array:     89083ms
$ g++ -Ofast benchmark.cpp 
$ ./a.out 
size=1000000 L=10
Calculation result is 181164
Duration of C style heap array:    93530ms
Calculation result is 181179
Duration of C99 style stack array: 80620ms
Calculation result is 181191
Duration of std::vector array:     78830ms

I risultati esatti varieranno, ma questo è abbastanza tipico sulla mia macchina.


0

Nella mia esperienza, a volte, solo a volte, vector<int>può essere molte volte più lento di int[]. Una cosa da tenere a mente è che i vettori dei vettori sono molto diversi int[][]. Poiché gli elementi probabilmente non sono contigui nella memoria. Ciò significa che è possibile ridimensionare diversi vettori all'interno di quello principale, ma la CPU potrebbe non essere in grado di memorizzare nella cache elementi come nel caso di int[][].

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.