2012-03-19 5 views
5

Quindi sono a conoscenza della domanda this e di altri SO che trattano il problema, ma la maggior parte di quelli si occupa delle complessità delle strutture dati (solo per copiare qui, collegato ciò ha teoricamente O (Comprehensive vector vs benchmark di elenchi collegati per inserimenti/eliminazioni randomizzate

capisco la complessità sembrerebbero indicare che l'elenco sarebbe stato meglio, ma io sono più preoccupato con le prestazioni reali mondo

Nota:. questa domanda è stata ispirata da slides 45 and 46 of Bjarne Stroustrup's presentation at Going Native 2012 dove parla di come il caching del processore e la localizzazione di riferimento aiutino davvero con i vettori, ma no t affatto (o abbastanza) con liste.

Domanda: C'è un buon modo per verificare questo utilizzando il tempo di CPU al contrario di parete di tempo, e ottenere un modo dignitoso di "casualmente" inserimento e cancellazione di elementi che può essere fatto in anticipo in modo che non influenza i tempi ?

Come bonus, sarebbe bello poter applicare questo a due strutture dati arbitrarie (ad esempio mappe vettoriali e hash o qualcosa del genere) per trovare le "prestazioni del mondo reale" su un determinato hardware.

+0

Non è affatto chiaro cosa intendi su "...inserendo ed eliminando elementi che possono essere fatti prima, in modo che non influenzino i tempi? "Se si sta inserendo e cancellando i tempi, si vuole chiaramente che l'inserimento e la cancellazione influenzino i tempi. –

+0

Sì, ma non voglio la determinazione di dove il punto di inserimento/cancellazione sarà quello di far parte dei tempi – soandos

+0

Oh, capisco ... E a parte cercare di rendere un elenco più competitivo, perché esattamente vorresti farlo? tutto ciò che riguarda l'uso del mondo reale. –

risposta

5

Credo che se dovessi provare qualcosa di simile, probabilmente sarei iniziare con qualcosa di codice in questo ordine:

#include <list> 
#include <vector> 
#include <algorithm> 
#include <deque> 
#include <time.h> 
#include <iostream> 
#include <iterator> 

static const int size = 30000; 

template <class T> 
double insert(T &container) { 
    srand(1234); 
    clock_t start = clock(); 
    for (int i=0; i<size; ++i) { 
     int value = rand(); 
     T::iterator pos = std::lower_bound(container.begin(), container.end(), value); 
     container.insert(pos, value); 
    } 
// uncomment the following to verify correct insertion (in a small container). 
// std::copy(container.begin(), container.end(), std::ostream_iterator<int>(std::cout, "\t")); 
    return double(clock()-start)/CLOCKS_PER_SEC; 
} 


template <class T> 
double del(T &container) { 
    srand(1234); 
    clock_t start = clock(); 
    for (int i=0; i<size/2; ++i) { 
     int value = rand(); 
     T::iterator pos = std::lower_bound(container.begin(), container.end(), value); 
     container.erase(pos); 
    } 
    return double(clock()-start)/CLOCKS_PER_SEC; 
}  

int main() { 
    std::list<int> l; 
    std::vector<int> v; 
    std::deque<int> d; 

    std::cout << "Insertion time for list: " << insert(l) << "\n"; 
    std::cout << "Insertion time for vector: " << insert(v) << "\n"; 
    std::cout << "Insertion time for deque: " << insert(d) << "\n\n"; 

    std::cout << "Deletion time for list: " << del(l) << '\n'; 
    std::cout << "Deletion time for vector: " << del(v) << '\n'; 
    std::cout << "Deletion time for deque: " << del(d) << '\n'; 

    return 0; 
} 

Dal momento che utilizza clock, questo dovrebbe dare il tempo processore non parete di tempo (anche se alcuni compilatori come MS VC++ si sbagliano). Non cerca di misurare il tempo per l'inserimento esclusivo del tempo per trovare il punto di inserimento, dal momento che 1) richiederebbe un po 'più di lavoro e 2) Non riesco ancora a capire cosa potrebbe ottenere. È certamente non rigoroso al 100%, ma vista la disparità che ne vedo, sarei un po 'sorpreso nel notare una differenza significativa rispetto ai test più accurati. Ad esempio, con MS VC++, ottengo:

Insertion time for list: 6.598 
Insertion time for vector: 1.377 
Insertion time for deque: 1.484 

Deletion time for list: 6.348 
Deletion time for vector: 0.114 
Deletion time for deque: 0.82 

con GCC ottengo:

Insertion time for list: 5.272 
Insertion time for vector: 0.125 
Insertion time for deque: 0.125 

Deletion time for list: 4.259 
Deletion time for vector: 0.109 
Deletion time for deque: 0.109 

Factoring il tempo di ricerca sarebbe un po 'non banale, perché si avrebbe in volta ogni iterazione separatamente . Avresti bisogno di qualcosa di più preciso di clock (di solito lo è) per ottenere risultati significativi da quello (più sull'ordine o leggendo un registro del ciclo di clock). Sentiti libero di modificarlo se lo ritieni opportuno - come ho detto sopra, mi manca la motivazione perché non riesco a vedere come sia una cosa sensata da fare.

1

Questo è il programma che ho scritto dopo aver visto quella conversazione. Ho provato a eseguire ogni test di temporizzazione in un processo separato per assicurarmi che gli allocatori non stessero facendo nulla di subdolo per alterare le prestazioni. Ho modificato il test per consentire i tempi della generazione del numero casuale. Se sei preoccupato, questo influisce in modo significativo sui risultati, puoi crearlo e sottrarre il tempo trascorso lì dal resto dei tempi. Ma ottengo zero tempo trascorso lì per tutto tranne N molto grande. Ho usato getrusage() che sono abbastanza sicuro non è portabile a Windows ma sarebbe facile da sostituire in qualcosa usando clock() o qualunque cosa tu voglia.

#include <assert.h> 
#include <algorithm> 
#include <iostream> 
#include <list> 
#include <vector> 
#include <stdlib.h> 
#include <time.h> 
#include <sys/time.h> 
#include <sys/resource.h> 


void f(size_t const N) 
{ 
    std::vector<int> c; 
    //c.reserve(N); 
    for (size_t i = 0; i < N; ++i) { 
     int r = rand(); 
     auto p = std::find_if(c.begin(), c.end(), [=](int a) { return a >= r; }); 
     c.insert(p, r); 
    } 
} 

void g(size_t const N) 
{ 
    std::list<int> c; 
    for (size_t i = 0; i < N; ++i) { 
     int r = rand(); 
     auto p = std::find_if(c.begin(), c.end(), [=](int a) { return a >= r; }); 
     c.insert(p, r); 
    } 
} 

int h(size_t const N) 
{ 
    int r; 
    for (size_t i = 0; i < N; ++i) { 
     r = rand(); 
    } 
    return r; 
} 

double usage() 
{ 
    struct rusage u; 
    if (getrusage(RUSAGE_SELF, &u) == -1) std::abort(); 
    return 
     double(u.ru_utime.tv_sec) + (u.ru_utime.tv_usec/1e6) + 
     double(u.ru_stime.tv_sec) + (u.ru_stime.tv_usec/1e6); 
} 


int 
main(int argc, char* argv[]) 
{ 
    assert(argc >= 3); 
    std::string const sel = argv[1]; 
    size_t const N = atoi(argv[2]); 

    double t0, t1; 
    srand(127); 

    if (sel == "vector") { 
     t0 = usage(); 
     f(N); 
     t1 = usage(); 
    } else if (sel == "list") { 
     t0 = usage(); 
     g(N); 
     t1 = usage(); 
    } else if (sel == "rand") { 
     t0 = usage(); 
     h(N); 
     t1 = usage(); 
    } else { 
     std::abort(); 
    } 

    std::cout 
     << (t1 - t0) 
     << std::endl; 

    return 0; 
} 

Per ottenere un set di risultati, ho utilizzato il seguente script di shell.

seq=`perl -e 'for ($i = 10; $i < 100000; $i *= 1.1) { print int($i), " "; }'` 
for i in $seq; do 
    vt=`./a.out vector $i` 
    lt=`./a.out list $i` 
    echo $i $vt $lt 
done