Jednym z punków była wyższość wektora na listą. Lista góruje nad wektorem, w takich operacjach jak wstawianie oraz usuwanie elementów, jednak proces wyszukania odpowiedniego elementu w kontenerze okazuje się być naprawdę wąskim gardłem. Wektor z kolei lepiej radzi sobie z wyszukiwaniem, gorzej natomiast z usuwaniem i wstawianiem. Dzięki współczesnym procesorem korzystającym z mechanizmu dedukcji skoku, wektor okazuje się zyskiwać ogromną przewagę. Mimo iż wykonuje znacznie więcej operacji w pamięci to będą one odbywały się w cache procesora, gdzie są naprawdę szybkie.
Opis testu.
Wygeneruj N losowych liczb i wstaw je tak by zachowana została posortowana kolejność np. 5 1 4 2:
- 5
- 1 5
- 1 4 5
- 1 2 4 5
Usuń elementy z losowej pozycji np. dla 1 2 0 0, będzie to:
- 1 2 4 5
- 1 4 5
- 1 4
- 4
TRnd get_rnd(const int max)
{
const int seed = 858446;
TBaseGeneratorType generator(seed);
boost::uniform_int<> uni_dist(0, max);
TRnd rnd(generator, uni_dist);
return rnd;
}
Pierwsza część testu - generyczna funkcja do wstawiania, zachowując posortowaną kolejność.template <typename T>
void insert_sort(T& con, const int size, TRnd& rnd)
{
for (int i = 0; i < size; ++i) {
const int value = rnd();
typename T::iterator it = std::find_if(con.begin(), con.end(),
value < _1);
con.insert(it, value);
}
}
Druga część testu - usuwanie wartości z losowej pozycji.template <typename T>
void erase_by_rand_pos(T& con, TRnd& rnd)
{
while (not con.empty()) {
const int pos = rnd() % con.size();
typename T::iterator it = con.begin();
std::advance(it, pos);
con.erase(it);
}
}
Ciało testu:#include <vector>
#include <list>
#include <algorithm>
#include <iostream>
#include <boost/lambda/lambda.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int.hpp>
#include <boost/random/variate_generator.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
using namespace std;
using namespace boost::lambda;
using namespace boost::posix_time;
typedef boost::mt19937 TBaseGeneratorType;
typedef boost::variate_generator<TBaseGeneratorType,
boost::uniform_int<> > TRnd;
int main(int argc, char **argv)
{
if (argc < 2) {
cout << "Missing size argument" << endl;
return -1;
}
TRnd rnd = get_rnd(std::numeric_limits<int>::max());
const int size = boost::lexical_cast<int>(argv[1]);
const ptime time_start = microsec_clock::local_time();
cout << "Start at: " << time_start << ", size = "<< size << endl;
// vector<int> container;
list<int> container;
insert_sort(container, size, rnd);
const ptime time_insert = microsec_clock::local_time();
cout << "Inserted sort: " << time_insert - time_start << endl;
erase_by_rand_pos(container, rnd);
const ptime time_remove = microsec_clock::local_time();
cout << "Erase at random pos: " << time_remove - time_insert << endl;
cout << "Whole process: " << time_remove - time_start << endl;
return 0;
}
Wyniki. Wszystkie testy były kompilowane za pomocą g++ (4.6.3) z flagą -O2. Bjarne musiał korzystać z bardziej wyrafinowanego algorytmu niż mój, bo w jego wersji cały proces dla 200000 elementów zajął ok. 500 sekund, w moim przypadku było to już 1116 sekund (sic!), dlatego też nie testowałem powyżej tej wartości. Nie testowałem też "preallocated list" bo nie jestem pewien co to dokładnie jest.I jeszcze jeden wykres, przedstawiający jak czasowo wyglądały poszczególne operacje, czyli wstawianie z wyszukiwaniem i usuwanie z wyszukiwaniem. Porównując dane, widać, że w przypadku listy, najwolniejszą operacją jest usuwanie z wyszukiwaniem, w przypadku wektora wstawianie z wyszukiwaniem. Usuwanie i wstawianie na wektorze powinny być jego słabym punktem, ale okazują się, że w połączeniu z wyszukiwaniem, są znacznie szybsze od tego co oferuje lista.