2012-04-15 25 views
11

Sto usando map<MyStruct, I*> map1;. Apparentemente il 9% del tempo totale della mia app è trascorso lì. In particolare su una riga di una delle mie principali funzioni. La mappa non è molto grande (< 1k quasi sempre, < 20 è comune).stl performance della mappa?

Esiste un'implementazione alternativa che potrei voler utilizzare? Penso che non dovrei scrivere il mio ma potrei se pensassi che fosse una buona idea.

Ulteriori informazioni: Controllo sempre prima di aggiungere un elemento. Se esiste una chiave, devo segnalare un problema. Poi, dopo un punto, userò la mappa pesantemente per le ricerche e non aggiungerò altri elementi.

+7

Senza codice sorgente, non possiamo davvero dire. Guarda anche la versione di 'insert' che restituisce una coppia (questo risponderà alla tua seconda domanda) –

+1

Potresti condividere informazioni sulla funzione di confronto su' MyStruct' che la mappa usa anche tu? – amit

+0

Puoi fornire qualche informazione in più su cosa stai facendo all'interno della funzione menzionata? Poiché la complessità della mappa di ricerca è O (log n), non sono sicuro da dove arriverà un miglioramento. –

risposta

6

Sarebbe più veloce fare semplicemente un insert e verificare se lo pair.second è false se la chiave esiste già?

come questo

if (myMap.insert(make_pair(MyStruct, I*)).second == false) 
{ 
    //report error 
} 
else 
    // inserted new value 

piuttosto che fare un find chiamata ogni volta?

+0

Ottima idea. Anche se l'aggiunta di memebers non è la linea con il problema delle prestazioni.Aggiungerò questo e guarderò la performance –

+0

Se rinvii il find a se hai effettivamente uno scontro, probabilmente elimini uno shedload di ricerche che non sono necessarie a meno che tu non stia facendo qualcosa di strano e generando più valori duplicati di quelli unici Il metodo 'insert' che ho dettagliato restituisce una coppia con' first' che è un iteratore del nuovo valore o valore duplicato, il secondo è un booleano con 'true' che significa successo e' false' che significa entrata duplicata. – EdChum

+0

Mi sento un po 'sciocco non fare inserti in questo modo. Devo aver avuto fretta o ho pensato che fosse un codice usa e getta. –

2

Nel modo in cui si utilizza la mappa, si effettuano ricerche sulla base di un'istanza MyStruct e, a seconda dell'implementazione particolare, il confronto richiesto può essere o meno costoso.

+0

hmm, non credo di averne bisogno in un ordine specifico. Mi rendo conto che la maggior parte del codice stava controllando se le due variabili fossero nulle e io penso che sia sempre così. La semplice rimozione di questo controllo lo ha reso abbastanza veloce da scomparire dal profiler (sono sorpreso). Se viene visualizzato di nuovo, giocherò di più con la funzione di confronto. +1 e possibilmente accettare. –

4

Potrebbe essere una soluzione lunga, ma per le piccole raccolte, a volte il fattore più critico è la prestazione cache.

Poiché std::map implementa un albero rosso-nero, che è [AFAIK] non molto efficiente in termini di cache, l'implementazione della mappa come std::vector<pair<MyStruct,I*>> potrebbe essere una buona idea e utilizzare la ricerca binaria lì [anziché le ricerche di mappe] , per lo meno dovrebbe essere efficiente una volta che si avvia solo la ricerca [smettere di inserire elementi], dal momento che lo std::vector è più probabile che si adatti alla cache rispetto allo map.

Questo fattore [cpu-cache] viene solitamente trascurato e nascosto come costante nella notazione O grande, ma per le raccolte di grandi dimensioni potrebbe avere un effetto maggiore.

+0

Probabilmente avrei una classe che usa due vettori internamente piuttosto che una coppia e la rende simile a una mappa. –

+1

@ acidzombie24: La 'coppia' è solo un suggerimento. Per quanto riguarda due vettori: concordo, probabilmente sarà anche meglio di un 'vector' di' pair's [meno campi -> meno utilizzo della memoria -> più probabile che l'intera mappa si adatti alla cache]. La risposta mirava solo a enfatizzare l'importanza della cache sulle piccole raccolte, e ricordare che di solito è molto più importante della notazione O grande per queste, poiché le costanti non dovrebbero essere trascurate. – amit

+1

Abbiamo visto grandi miglioramenti nel nostro codice in alcuni casi quando si passa dalla mappa al vettore. Sospettiamo che le prestazioni di memorizzazione nella cache siano molto maggiori con il vettore. –

1

Esiste un'implementazione alternativa che potrei voler utilizzare? Penso che non dovrei scrivere il mio ma potrei se pensassi che fosse una buona idea.

Se si capisce il problema abbastanza bene, si dovrebbe specificare come la vostra implementazione sarà superiore.

È map la struttura corretta? Se è così, allora l'implementazione della tua libreria standard sarà probabilmente di buona qualità (ben ottimizzata).

È possibile semplificare il confronto MyStruct?

Dov'è il problema - ridimensionamento? consultare?

Hai minimizzato la copia e l'assegnazione dei costi per le tue strutture?

+0

Problema: ricerca. Struct corretto: forse. Devo trovare una struct by key (che deve essere confrontata con altre struct) e l'oggetto di interfaccia ad esso associato. Non penso che l'ordine sia un problema in quanto tipicamente piccolo. Copia/Assegna: Beh, assegno copiando un ptr. È immutabile quindi non devo preoccuparmi di essere cancellato. –

+0

Stavo controllando se entrambe le strutture sono nulle nel mio cmp func. Rimozione lo ha reso abbastanza veloce da non presentarsi più. +1 per la risposta generale. –

5

Invece di map è possibile provare unordered_map che utilizza le chiavi hash, anziché un albero, per trovare gli elementi. This answer fornisce alcuni suggerimenti su quando preferire unordered_map su map.

+3

Per le piccole raccolte, le mappe hash sono * di solito * più lente degli alberi rosso-neri, quindi mi aspetto che il suo utilizzo in questo caso abbia solo un effetto negativo. – amit

+0

@amit: anche per una piccola raccolta con 20 elementi, [questo semplice confronto] (http://ideone.com/YupK4) - sotto la restrizione di un semplice tipo di chiave - mi dà una prestazione migliore per il 'unordered_set '. Un confronto reale includerebbe "MyStruct" e una funzione di hash per esso. –

+0

Ho guardato unordered_map. Dopo aver letto il messaggio di errore (è grande ...) mi rendo conto che ho bisogno di implementare una funzione di hashing. Non ho idea di come farlo. Come faccio a cancellare un carattere *? Uno che può contenere da 1 a 20 carri (o più) –

35

Per prima cosa devi capire cos'è una mappa e quali sono le operazioni che stai facendo. A std::map è un albero binario bilanciato, la ricerca richiederà le operazioni O(log N), ognuna delle quali è un confronto delle chiavi più alcuni extra che è possibile ignorare nella maggior parte dei casi (gestione dei puntatori). L'inserimento impiega all'incirca lo stesso tempo per individuare il punto di inserimento, oltre all'assegnazione del nuovo nodo, l'effettivo inserimento nell'albero e il ribilanciamento. La complessità è ancora O(log N) sebbene le costanti nascoste siano più alte.

Quando si tenta di determinare se una chiave è presente nella mappa prima dell'inserimento, si incorre nel costo della ricerca e, se non riesce, lo stesso costo per individuare il punto di inserimento. Puoi evitare il costo aggiuntivo utilizzando std::map::insert che restituisce una coppia con un iteratore e un bool che ti dice se l'inserimento è effettivamente avvenuto o se l'elemento era già lì.

Oltre a ciò, è necessario capire quanto costoso è quello di confrontare le chiavi, che cade fuori di ciò che gli spettacoli interrogativi (MyStruct poteva contenere un solo int o mille di essi), che è qualcosa che devi prendere in account.

Infine, potrebbe essere il caso che una map non è la struttura dei dati più efficiente per le vostre esigenze, e si potrebbe prendere in considerazione utilizzando un (tabella hash) std::unordered_map che ha previsto inserimenti di tempo costanti (se la funzione di hash non è orribile) o per piccoli set di dati anche un array ordinato ordinario (o std::vector) su cui è possibile utilizzare la ricerca binaria per individuare gli elementi (questo ridurrà il numero di allocazioni, al costo di inserimenti più costosi, ma se i tipi mantenuti sono abbastanza piccoli potrebbe valerne la pena)

Come sempre con le prestazioni, misurare e quindi cercare di capire dove viene trascorso il tempo. Si noti inoltre che il 10% del tempo trascorso in una particolare funzione o struttura dati potrebbe essere molto o quasi nulla, a seconda di quale sia l'applicazione. Ad esempio, se la tua applicazione sta solo eseguendo ricerche e inserimenti in un set di dati, e che richiede solo il 10% della CPU, hai molto da ottimizzare ovunque!

+0

Ottima risposta. Un suggerimento non ordinato (hash) potrebbe essere negativo poiché la dimensione del tavolo è piccola. Userò sicuramente l'inserto nella mia altra posizione. Colpisci tutti i punti importanti –

+2

In alternativa a un inserto cieco puoi usare 'lower_bound', controlla il tasto, e quindi usa l'inserimento con suggerimenti (se vuoi evitare una costruzione di oggetti potenzialmente costosa). –

+0

La struttura è in realtà un modello di wrapper veloce che ho scritto che contiene T *. Il punto è così ops come '<' e '==' farà '* ptr OP * other' quindi non confronta con l'indirizzo ptr. Rende la mia vita immensamente più facile quando si usano i contenitori. Questo è un altro buon suggerimento. +1 @KerrekSB –

1

Come indicato nei commenti, senza codice corretto, ci sono poche risposte universali da darti. Tuttavia, se MyStruct è davvero enorme, la copia dello stack potrebbe essere costosa. Forse ha senso per memorizzare i puntatori ai MyStruct e implementare il proprio meccanismo di confronto:

template <typename T> struct deref_cmp { 
    bool operator()(std::shared_ptr<T> lhs, std::shared_ptr<T> rhs) const { 
    return *lhs < *rhs; 
    } 
}; 

std::map<std::shared_ptr<MyStruct>, I*, deref_cmp<MyStruct>> mymap; 

Tuttavia, questo è qualcosa che si dovrà al profilo. È potrebbe cose di velocità.

Si potrebbe cercare un elemento come questo

template <typename T> struct NullDeleter { 
    void operator()(T const*) const {} 
}; 
// needle being a MyStruct 
mymap.find(std::shared_ptr<MyStruct>(&needle,NullDeleter())); 

Inutile dire che non v'è più possibilità di ottimizzare.

+0

MyStruct è in realtà T * dove T è un modello;). Vedi il mio primo commento a Johann Gerell answer –

+1

Beh, in tal caso non devi preoccuparti di questo reindirizzamento. – bitmask

+0

sì. quindi +1 per il suggerimento comunque :) –