Giusto per mettere in campo qualche numero:
codice:
matteo@teoubuntu:~/cpp/vectorbenchmark2$ ./vectorbenchmark2
Inserire il numero di elementi: 2000000
Inserire il numero di iterazioni: 500
Verranno allocati almeno 8000000 bytes per volta.
Avvio benchmark std::vector<int> (push_back/iterator, senza reserve)... benchmark completato.
Risultati: 32.10 +/- 0.69 ms
Avvio benchmark std::vector<int> (push_back/iterator, con reserve)... benchmark completato.
Risultati: 21.03 +/- 0.23 ms
Avvio benchmark std::vector<int> (dimensioni nel costruttore/operator[])... benchmark completato.
Risultati: 19.57 +/- 0.52 ms
Avvio benchmark new int[]... benchmark completato.
Risultati: 20.02 +/- 0.15 ms
Avvio benchmark malloc+memset... benchmark completato.
Risultati: 20.02 +/- 0.15 ms
Avvio benchmark malloc... benchmark completato.
Risultati: 18.14 +/- 0.37 ms
Avvio benchmark std::list<int>... benchmark completato.
Risultati: 111.2 +/- 3.6 ms
matteo@teoubuntu:~/cpp/vectorbenchmark2$ ./vectorbenchmark2
Inserire il numero di elementi: 20000000
Inserire il numero di iterazioni: 10
Verranno allocati almeno 80000000 bytes per volta.
Avvio benchmark std::vector<int> (push_back/iterator, senza reserve)... benchmark completato.
Risultati: 365.8 +/- 5.5 ms
Avvio benchmark std::vector<int> (push_back/iterator, con reserve)... benchmark completato.
Risultati: 261.1 +/- 8.3 ms
Avvio benchmark std::vector<int> (dimensioni nel costruttore/operator[])... benchmark completato.
Risultati: 235.70 +/- 0.67 ms
Avvio benchmark new int[]... benchmark completato.
Risultati: 247.20 +/- 0.63 ms
Avvio benchmark malloc+memset... benchmark completato.
Risultati: 244.30 +/- 0.82 ms
Avvio benchmark malloc... benchmark completato.
Risultati: 238.20 +/- 0.63 ms
Avvio benchmark std::list<int>... benchmark completato.
Risultati: 1207 +/- 142 ms
Ciascun benchmark consiste nell'allocare la struttura dati, aggiungerci tanti numeri casuali quanti specificati nella prima richiesta (tramite push_back nel caso dei container STL, con operator[] in un caso per il vector, "viaggiando" via puntatore sugli array allocati con new/malloc) e rileggerli (in una variabile dichiarata volatile, in modo che il compilatore non ottimizzi via il ciclo di rilettura).
Ciascun benchmark è eseguito tante volte quanto indicato nel secondo dato richiesto dal programma, e i risultati di ciascun test sono memorizzati in un vector<double>; i dati mostrati derivano dall'applicazione di media e deviazione standard sui vari dataset, e sono espressi in millisecondi.
Dai dati emerge che, almeno per quantità di dati paragonabili a quelle di cui parla freetom, un std::vector è solo leggermente più lento di un array stile C allocato nell'heap. In particolare, la configurazione migliore per il vector è dimensionarlo fin dall'inizio nel costruttore (il che equivale a richiamare resize) e poi usare operator[] per l'accesso agli elementi. Credo che la configurazione reserve/push_back sia leggermente più lenta perché il push_back deve anche aggiornare il numero di elementi presenti nel vector, mentre l'operator[] fornisce proprio accesso grezzo alla memoria.
Delle alternative, l'unica con cui il vector nella configurazione migliore trova avversari all'altezza è la malloc semplice (senza inizializzazione), che in genere vince ma di molto poco; quando gli elementi aumentano ho notato che le prestazioni sono pressoché identiche, forse perché aumentando il numero di elementi sparisce il costo fisso delle operazioni di inizializzazione del vector.
La lista è talmente lenta rispetto a tutte le alternative (compreso il vettore autoespandente) che non vale neanche la pena di parlarne.
Tra l'altro la sua deviazione standard nel secondo caso è particolarmente grande, credo perché alla prima iterazione si è verificato un page fault o roba del genere.
Informazioni sull'ambiente di test:
codice:
matteo@teoubuntu:~/cpp/vectorbenchmark2$ g++ --version
g++ (Ubuntu/Linaro 4.4.4-14ubuntu5) 4.4.5
Copyright (C) 2010 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
matteo@teoubuntu:~/cpp/vectorbenchmark2$ uname -a
Linux teoubuntu 2.6.35-24-generic #42-Ubuntu SMP Thu Dec 2 02:41:37 UTC 2010 x86_64 GNU/Linux
matteo@teoubuntu:~/cpp/vectorbenchmark2$ make
g++ -O3 -Wall -Wextra -ansi -pedantic -MMD -MF BenchmarkFunctors.o.d -c -o BenchmarkFunctors.o BenchmarkFunctors.cpp
g++ -O3 -Wall -Wextra -ansi -pedantic -MMD -MF main.o.d -c -o main.o main.cpp
g++ -O3 -Wall -Wextra -ansi -pedantic -MMD -MF Utils.o.d -c -o Utils.o Utils.cpp
g++ -O3 -Wall -Wextra -ansi -pedantic BenchmarkFunctors.o main.o Utils.o -o vectorbenchmark2
Il PC di prova ha processore AMD Phenom x4 955 (3.2 GHz x 4), con 4 GB di DDR3.
Comunque, per chi fosse interessato al codice (warning: è praticamente privo di commenti, ci sono template a palate perché volevo fare un piccolo framework di benchmarking riutilizzabile), qui c'è il tar.gz; per poterlo compilare è necessario avere installate le librerie boost. La classe Cronometer è pensata per sistemi POSIX (usa gettimeofday), ma su Windows la si può reimplementare facilmente con la QueryPerformanceCounter.
Per inciso, tutti i tempi misurati per 2 milioni di elementi sono attorno ai 20 ms (salvo il vector non predimensionato che va attorno ai 32 e la lista che si aggira sui 110), per cui non vedo come ci possa essere un problema di prestazioni su tempi così brevi.