2012-06-04 9 views
6

L'efficienza di map::insert(iterator position, const value& k) può essere notevolmente migliorata fornendo il valore appropriato nella posizione del parametro.Inserimento rapido di valori in una mappa con un numero intero crescente come chiave?

Se si utilizzano numeri interi come chiave e ogni inserimento è eseguito con un numero maggiore di tutti i tasti precedentemente inseriti, è possibile velocizzare l'operazione ::insert quando si fornisce l'iteratore ::end() della mappa?

Qualcosa di simile:

myMap.insert(myMap.end() , make_pair(next_number , myValue)); 

dove myMap è di tipo map<uint64_t,MyType> e next_number è un grande incremento ogni numero intero.

Edit:

La risposta a questa domanda potrebbe essere diverso a seconda che i dati memorizzati nella map è denso o no (vedi la discussione sotto). Quindi, poniamo la domanda in entrambi i modi: una volta che è densa una volta che non lo è. Ancora curioso Forse misurare risponderà.

+0

Esattamente. Nel caso in cui la chiave sia 'uint64_t' e sempre crescente, è il" suggerimento migliore "dare a' :: end() 'come suggerimento? – ritter

+0

Scommetto che 'insert' richiede un iteratore dereferenziabile (che' end() 'non lo è), ma non ne sono assolutamente certo. –

+1

@ eq-: No, l'iteratore di posizione deve essere solo un iteratore valido, non uno dereferenziabile. E poiché l'idea è che è una posizione che il tuo nuovo elemento molto probabilmente precede, sarebbe una specie di inutile se non consentisse end(). – abarnert

risposta

5

Per rispondere direttamente alla domanda posta, il C++ specifiche dicono che:

  • In C++ 03, l'inserimento in una mappa con a.insert(p,t) deve essere ammortizzato con una complessità costante (anziché logaritmica) se t viene inserito a destra dopop.
  • In C++ 11, l'inserimento in una mappa con a.insert(p,t) deve essere ammortizzato con una complessità costante se t viene inserito a destra prima dellop.

e in nessun caso p deve essere dereferenziabile. Pertanto, nel tuo caso, è probabile che a.end() sia il miglior suggerimento in C++ 11, ma non in C++ 03.

+0

Questa è una situazione contorta. Ottengo il peggio possibile se metto il suggerimento solo una posizione a destra del miglior suggerimento? – ritter

+0

@Frank: è sempre possibile diminuire in modo condizionale in base al valore della macro '__cplusplus'. – ildjarn

+0

Spero che l'implementazione pratica sia buona con il suggerimento prima o dopo. Altrimenti è un cambiamento drastico. –

1

Qualsiasi suggerimento è semplicemente un suggerimento, qualcosa da provare e misurare. Non possiamo davvero dirvi il modo più performante per fare l'inserimento, dovreste misurare per il vostro caso d'uso specifico e vedere cosa è meglio.

Se la tua mappa è compatta e densa (quasi tutti gli elementi da 0 - max chiave sono occupati da dati reali) e il tasto max è sufficientemente basso da essere un indice di array ragionevole che potresti passare usando std::vector<value> e inserendo sempre su fine. Dal momento che è in continua crescita, dovrai occasionalmente riallocare il vettore (di solito è quando il vettore raddoppia). Questo può essere costoso, ma generalmente l'inserimento sarà molto economico. Non si ha a che fare con il potenziale ribilanciamento di un albero binario e il vettore è estremamente cache-friendly per altri scopi.

Se lo spazio chiave della mappa non è compatto/denso e la chiave massima è così grande che il suo indice non è concepibile in memoria, l'inserimento con un suggerimento sarà la soluzione migliore.

Se l'ordine non è importante, è possibile provare std::unordered_map. Questa è un'implementazione della tabella hash. Quindi il costo di inserimento si riferirà alla qualità e alla velocità dell'hash. Dovrebbe essere banale e veloce prendere la chiave a 64 bit e trasformarla in un hash size_t (size_t potrebbe anche essere 64 bit).

Ma non c'è bisogno di prendere la mia parola per esso, misurarlo, e vedere di persona ...

+2

Di solito direi la stessa cosa da solo, ma in questo caso il parametro suggerimento è stato aggiunto all'interfaccia standard per un motivo. –

+0

I miei dati sono l'opposto di ciò che chiamate "denso". Ho 2 milioni di inserzioni (in 20 secondi) e il numero medio di elementi nel contenitore (mappa) è intorno a 20. Ma la chiave (come ho detto) è in costante aumento. Che ne dici? Mappa, con un suggerimento fino alla fine? Oppure nel mio caso un ribilanciamento dell'albero troppo spesso? – ritter

+0

@Frank Proverei una mappa non ordinata se l'ordine non ha importanza. Dirò anche che i contenitori STL sono abbastanza generici. Nella mia esperienza, ci sono volte in cui le prestazioni sono così importanti che vale la pena scrivere una struttura dati creata per il tuo scopo specifico. Certo, allora puoi facilmente spararti ai piedi. Come sempre misura e considera attentamente le tue opzioni. –

2

vorrei suggerire due cose:

  • preferiscono std::unordered_map in questo caso, l'inserimento di sempre ad un'estremità è uno scenario peggiore per gli alberi rosso-neri
  • utilizzare un allocatore personalizzato se new dimostra di essere un fastidio, da quello che stai parlando di una strategia di allocazione della piscina potrebbe essere utilizzato

Si noti che C++ 11 consente l'utilizzo di allocatori di stato, quindi dovrebbe essere abbastanza semplice fornire un allocatore che si adatti e abbia un incorporato std::vector<T> dentro e usarlo come una pila.

0

Ho eseguito alcune misurazioni da quando mi sono imbattuto recentemente in questo problema.

Ho una grande mappa, con molti dati, i dati sono raramente inseriti, il 99% delle volte è solo accessibile e modificato sul posto usando i riferimenti. Tuttavia, questi dati devono essere salvati su disco e caricati di nuovo. Soluzioni come "usa una mappa non ordinata", sembrano un modo economico e poco costoso di sbagliare, la mappa ordinata era il modo giusto per me, dato che i dati sono ordinati. Unico problema stava caricando dal file.

volevo sapere qual è il vero costo di questa operazione e come accelerarlo, quindi, ho misurato:

// Example program 
#include <iostream> 
#include <string> 
#include <map> 
#include <vector> 
#include <time.h> 

std::vector<int> amount = {100, 1000, 10000, 100000, 1000000, 5000000}; 

int main() 
{ 
    for(int j=0; j<amount.size(); j++) 
    { 
    clock_t tStart = clock(); 

    std::map<int,int> mymap; 
    for(int i=0; i<amount[j]; i++){ 
     mymap[i] = i; 
    } 

    printf("Time taken []: %.2fs\n", (double)(clock() - tStart)); 
    } 
    for(int j=0; j<amount.size(); j++) 
    { 
    clock_t tStart = clock(); 

    std::map<int,int> mymap; 
    mymap[0] = 0; 
    auto it = mymap.begin(); 
    for(int i=1; i<amount[j]; i++){ 
     it = mymap.insert(it, std::pair<int,int>(i,i)); 
    } 

    printf("Time taken insert end()-1: %.2fns\n", (double)(clock() - tStart)); 
    } 
    for(int j=0; j<amount.size(); j++) 
    { 
    clock_t tStart = clock(); 

    std::map<int,int> mymap; 
    for(int i=1; i<amount[j]; i++){ 
     mymap.insert(mymap.end(), std::pair<int,int>(i,i)); 
    } 

    printf("Time taken insert end(): %.2fns\n", (double)(clock() - tStart)); 
    } 
    for(int j=0; j<amount.size(); j++) 
    { 
    clock_t tStart = clock(); 

    std::map<int,int> mymap; 
    for(int i=0; i<amount[j]; i++){ 
     mymap.insert(mymap.begin(), std::pair<int,int>(i,i)); 
    } 

    printf("Time taken insert begin(): %.2fs\n", (double)(clock() - tStart)); 
    } 
    return 0; 
} 

Risultati:

Time in ns 
N  end()-1 end() begin() [] 
100  12  8  22  12 
1000 77  54  188  97 
10000 763  532  2550 1174 
100000 7609 6042 23612 17164 
1000000 75561 62048 270476 272099 
5000000 362463 306412 1827807 1687904 

enter image description here enter image description here

Sommario:

  • SI c'è guadagno, guadagno enorme, senza alcun vero inconveniente. Estremamente migliore di una mappa non ordinata quando i dati sono ordinati, estremamente utile per il caso di salvare su un file una mappa e ricrearla.

  • L'ora di inserimento se il suggerimento è corretto è la stessa indipendentemente dal numero di elementi. Quindi non è necessario ricorrere a una mappa non truccata per avere un tempo costante.

  • Nel peggiore dei casi si potrebbe perdere alcuni se il suggerimento è il suggerimento peggiore possibile. Non vedo più niente da fare con gli inserti senza un suggerimento, specialmente se si hanno conoscenze su dove verranno inseriti i dati. E la maggior parte delle volte lo fai.

Problemi correlati