Une récente discussion unordered_map
en C ++ m'a fait réaliser que je devrais utiliser unordered_map
dans la plupart des cas où je l'ai utilisé map
auparavant, en raison de l'efficacité de la recherche ( O (1) amorti vs O (log n) ). La plupart du temps, j'utilise une carte, j'utilise soit int
ou std::string
comme type de clé; par conséquent, je n'ai aucun problème avec la définition de la fonction de hachage. Plus j'y réfléchissais, plus je me rendais compte que je ne trouvais aucune raison d'utiliser un std::map
sur un std::unordered_map
dans le cas de clés avec des types simples - j'ai jeté un coup d'œil aux interfaces, et je n'ai trouvé aucun des différences importantes qui auraient un impact sur mon code.
D' où la question: est - il une vraie raison d'utiliser std::map
plus std::unordered_map
dans le cas des types simples comme int
et std::string
?
Je demande d'un point de vue strictement programmatique - je sais que ce n'est pas entièrement considéré comme standard et que cela peut poser des problèmes de portage.
De plus, je m'attends à ce que l'une des bonnes réponses soit «c'est plus efficace pour des ensembles de données plus petits» en raison d'un surcoût plus petit (est-ce vrai?) - je voudrais donc limiter la question aux cas où la quantité de clés n'est pas trivial (> 1 024).
Edit: duh, j'ai oublié l'évidence (merci GMan!) - oui, les cartes sont commandées bien sûr - je le sais, et je cherche d'autres raisons.
la source
Réponses:
N'oubliez pas que cela
map
garde ses éléments ordonnés. Si vous ne pouvez pas abandonner cela, vous ne pouvez évidemment pas l'utiliserunordered_map
.Une autre chose à garder à l'esprit est qu'elle
unordered_map
utilise généralement plus de mémoire.map
a juste quelques pointeurs de ménage et de la mémoire pour chaque objet. A l'inverse,unordered_map
a un grand tableau (ceux-ci peuvent devenir assez gros dans certaines implémentations), puis de la mémoire supplémentaire pour chaque objet. Si vous devez être conscient de la mémoire, celamap
devrait s'avérer mieux, car il manque le grand tableau.Donc, si vous avez besoin d'une recherche-récupération pure, je dirais que
unordered_map
c'est la voie à suivre. Mais il y a toujours des compromis, et si vous ne pouvez pas vous les permettre, vous ne pouvez pas l'utiliser.Juste par expérience personnelle, j'ai trouvé une énorme amélioration des performances (mesurée, bien sûr) lors de l'utilisation
unordered_map
au lieu d'map
une table de recherche d'entité principale.D'un autre côté, j'ai trouvé qu'il était beaucoup plus lent d'insérer et de retirer des éléments à plusieurs reprises. C'est génial pour une collection d'éléments relativement statiques, mais si vous faites des tonnes d'insertions et de suppressions, le hachage + le regroupement semble s'additionner. (Remarque, cela a duré de nombreuses itérations.)
la source
unordered_map
et réservez au début - payez-vous toujours une pénalité de nombreuses insertions? Supposons que vous n'insérez qu'une seule fois lorsque vous avez créé la table de recherche, puis que vous n'y lisez plus tard.Si vous voulez comparer la vitesse de vos
std::map
etstd::unordered_map
mises en œuvre, vous pouvez utiliser Google sparsehash projet qui a un programme de time_hash_map en temps eux. Par exemple, avec gcc 4.4.2 sur un système Linux x86_64la source
Je ferais à peu près écho au même point que GMan a fait valoir: selon le type d'utilisation, il
std::map
peut être (et est souvent) plus rapide questd::tr1::unordered_map
(en utilisant l'implémentation incluse dans VS 2008 SP1).Il y a quelques facteurs compliquant à garder à l'esprit. Par exemple, dans
std::map
, vous comparez des clés, ce qui signifie que vous ne regardez que suffisamment le début d'une clé pour faire la distinction entre les sous-branches droite et gauche de l'arborescence. D'après mon expérience, la seule fois où vous regardez une clé entière est si vous utilisez quelque chose comme int que vous pouvez comparer en une seule instruction. Avec un type de clé plus typique comme std :: string, vous ne comparez souvent que quelques caractères.Une fonction de hachage décente, en revanche, examine toujours la clé entière . IOW, même si la recherche de table est à complexité constante, le hachage lui-même a une complexité à peu près linéaire (bien que sur la longueur de la clé, pas sur le nombre d'éléments). Avec de longues chaînes comme clés, un
std::map
pourrait terminer une recherche avantunordered_map
même de commencer sa recherche.Deuxièmement, bien qu'il existe plusieurs méthodes de redimensionnement des tables de hachage, la plupart d'entre elles sont assez lentes - au point qu'à moins que les recherches ne soient considérablement plus fréquentes que les insertions et les suppressions, std :: map sera souvent plus rapide que
std::unordered_map
.Bien sûr, comme je l'ai mentionné dans le commentaire de votre question précédente, vous pouvez également utiliser un tableau d'arbres. Cela présente à la fois des avantages et des inconvénients. D'une part, il limite le pire des cas à celui d'un arbre. Il permet également une insertion et une suppression rapides, car (au moins quand je l'ai fait), j'ai utilisé une table de taille fixe. L'élimination de tout redimensionnement de table vous permet de garder votre table de hachage beaucoup plus simple et généralement plus rapide.
Un autre point: les exigences pour le hachage et les cartes arborescentes sont différentes. Le hachage nécessite évidemment une fonction de hachage et une comparaison d'égalité, où les cartes ordonnées nécessitent une comparaison inférieure à. Bien sûr, l'hybride que j'ai mentionné nécessite les deux. Bien sûr, dans le cas commun de l'utilisation d'une chaîne comme clé, ce n'est pas vraiment un problème, mais certains types de clés conviennent mieux à l'ordre que le hachage (ou vice versa).
la source
dynamic hashing
techniques, qui consistent à avoir une période de transition où chaque fois que vous insérez un élément, vous remaniez également lesk
autres éléments. Bien sûr, cela signifie que pendant la transition, vous devez rechercher 2 tables différentes ...unordered_map
faut également confirmer une correspondance de hachage avec une comparaison complète, donc tout dépend des parties du processus de recherche que vous contrastez.J'ai été intrigué par la réponse de @Jerry Coffin, qui a suggéré que la carte ordonnée présenterait des augmentations de performances sur de longues chaînes, après quelques expérimentations (qui peuvent être téléchargées à partir de pastebin ), j'ai trouvé que cela ne semble vrai que pour les collections de chaînes aléatoires, lorsque la carte est initialisée avec un dictionnaire trié (qui contient des mots avec des quantités considérables de chevauchement de préfixes), cette règle tombe en panne, probablement en raison de la profondeur d'arbre accrue nécessaire pour récupérer la valeur. Les résultats sont indiqués ci-dessous, la 1ère colonne de nombre est le temps d'insertion, la 2ème est le temps de récupération.
la source
std::map
surpasse généralementstd::unordered_map
, en particulier pour les clés entières, mais ~ 100 clés, il semble qu'il perd son avantage etstd::unordered_map
commence à gagner. Insérer une séquence déjà ordonnée dans unstd::map
est très mauvais, vous obtiendrez son pire scénario (O (N)).Je voudrais simplement souligner que ... il existe de nombreux types de par
unordered_map
.Recherchez l'article Wikipedia sur la carte de hachage. Selon l'implémentation utilisée, les caractéristiques en termes de recherche, d'insertion et de suppression peuvent varier de manière assez significative.
Et c'est ce qui m'inquiète le plus avec l'ajout de
unordered_map
la STL: ils devront choisir une implémentation particulière car je doute qu'ils iront sur laPolicy
route, et donc nous serons coincés avec une implémentation pour l'utilisation moyenne et rien pour les autres cas ...Par exemple, certaines cartes de hachage ont un réhachage linéaire, où au lieu de re-hacher toute la carte de hachage à la fois, une partie est re-hachée à chaque insertion, ce qui aide à amortir le coût.
Un autre exemple: certaines cartes de hachage utilisent une simple liste de nœuds pour un compartiment, d'autres utilisent une carte, d'autres n'utilisent pas de nœuds mais trouvent l'emplacement le plus proche et enfin certaines utiliseront une liste de nœuds mais la réorganiseront afin que le dernier élément accédé est à l'avant (comme une chose en cache).
Donc, pour le moment, j'ai tendance à préférer le
std::map
ou peut-être unloki::AssocVector
(pour les ensembles de données figés).Ne vous méprenez pas, j'aimerais utiliser le
std::unordered_map
et je le pourrai à l'avenir, mais il est difficile de "faire confiance" à la portabilité d'un tel conteneur quand on pense à toutes les façons de le mettre en œuvre et aux différentes performances qui en résultent de cela.la source
Différences importantes qui n'ont pas vraiment été mentionnées de manière adéquate ici:
map
maintient les itérateurs de tous les éléments stables, en C ++ 17, vous pouvez même déplacer des éléments de l'unmap
à l'autre sans invalider les itérateurs (et s'ils sont correctement implémentés sans allocation potentielle).map
les horaires des opérations individuelles sont généralement plus cohérents car ils n'ont jamais besoin d'allocations importantes.unordered_map
l'utilisationstd::hash
telle qu'implémentée dans libstdc ++ est vulnérable au DoS si elle est alimentée par des entrées non fiables (elle utilise MurmurHash2 avec une valeur de départ constante - pas que l'amorçage aiderait vraiment, voir https://emboss.github.io/blog/2012/12/14/ rupture-murmure-hachage-inondation-dos-rechargé / ).la source
Les tables de hachage ont des constantes plus élevées que les implémentations de cartes courantes, qui deviennent importantes pour les petits conteneurs. La taille maximale est de 10, 100 ou peut-être même 1 000 ou plus? Les constantes sont les mêmes que jamais, mais O (log n) est proche de O (k). (N'oubliez pas que la complexité logarithmique est toujours très bonne.)
Ce qui fait une bonne fonction de hachage dépend des caractéristiques de vos données; donc si je ne prévois pas de regarder une fonction de hachage personnalisée (mais je peux certainement changer d'avis plus tard, et facilement depuis que je tape typiquement près de tout) et même si les valeurs par défaut sont choisies pour fonctionner correctement pour de nombreuses sources de données, je trouve l'ordre la nature de la carte pour être assez utile au départ que je mets toujours par défaut à la carte plutôt qu'une table de hachage dans ce cas.
De plus, vous n'avez même pas à penser à écrire une fonction de hachage pour d'autres types (généralement UDT), et à écrire simplement op <(que vous voulez quand même).
la source
map
et une deunordered_map
, avec une certaine plate-forme et une certaine taille de cache, et faire une analyse complexe. : PDes raisons ont été données dans d'autres réponses; en voici un autre.
Les opérations std :: map (arbre binaire équilibré) sont amorties O (log n) et le pire des cas O (log n). Les opérations std :: unordered_map (table de hachage) sont amorties O (1) et le pire des cas O (n).
En pratique, la table de hachage "hoquet" de temps en temps avec une opération O (n), ce qui peut ou non être quelque chose que votre application peut tolérer. S'il ne le tolère pas, vous préféreriez std :: map à std :: unordered_map.
la source
Sommaire
En supposant que la commande n'est pas importante:
std::unordered_map
std::map
. C'est parce que les lectures le sontO(log n)
.std::map
bonne option.std::unordered_map
.Contexte historique
Dans la plupart des langues, la carte non ordonnée (ou dictionnaires basés sur le hachage) est la carte par défaut, mais en C ++, vous obtenez la carte ordonnée comme carte par défaut. Comment est-ce arrivé? Certaines personnes supposent à tort que le comité C ++ a pris cette décision dans leur sagesse unique, mais la vérité est malheureusement plus laide que cela.
Il est largement admis que C ++ s'est retrouvé avec une carte ordonnée par défaut car il n'y a pas trop de paramètres sur la façon dont ils peuvent être implémentés. D'un autre côté, les implémentations basées sur le hachage ont des tonnes de choses à dire. Donc, pour éviter les blocages dans la normalisation, ils se sont juste entendus avec la carte ordonnée. Vers 2005, de nombreuses langues avaient déjà de bonnes implémentations d'implémentation basée sur le hachage et il était donc plus facile pour le comité d'accepter de nouvelles
std::unordered_map
. Dans un monde parfait,std::map
aurait été désordonné et nous aurionsstd::ordered_map
comme type distinct.Performance
Ci-dessous, deux graphiques devraient parler d'eux-mêmes ( source ):
la source
J'ai récemment fait un test qui fait 50000 fusionner et trier. Cela signifie que si les clés de chaîne sont identiques, fusionnez la chaîne d'octets. Et la sortie finale doit être triée. Cela inclut donc une recherche pour chaque insertion.
Pour la
map
mise en œuvre, il faut 200 ms pour terminer le travail. Pour leunordered_map
+map
, il faut 70 ms pour l'unordered_map
insertion et 80 ms pour l'map
insertion. L'implémentation hybride est donc 50 ms plus rapide.Nous devrions réfléchir à deux fois avant d'utiliser le
map
. Si vous avez seulement besoin de trier les données dans le résultat final de votre programme, une solution hybride peut être meilleure.la source
Petit ajout à tout ce qui précède:
Mieux utiliser
map
, lorsque vous avez besoin d'obtenir des éléments par plage, car ils sont triés et vous pouvez simplement les parcourir d'une frontière à l'autre.la source
De: http://www.cplusplus.com/reference/map/map/
"En interne, les éléments d'une carte sont toujours triés par sa clé selon un critère de classement faible strict spécifique indiqué par son objet de comparaison interne (de type Compare).
les conteneurs de carte sont généralement plus lents que les conteneurs unordered_map pour accéder aux éléments individuels par leur clé, mais ils permettent l'itération directe sur les sous-ensembles en fonction de leur ordre. "
la source