Uma conversa recente sobre unordered_map
C ++ me fez perceber que eu deveria usar unordered_map
na maioria dos casos em que usei map
antes, devido à eficiência da pesquisa ( amortizado O (1) vs. O (log n) ). Na maioria das vezes eu uso um mapa, eu uso int
ou std::string
como o tipo de chave; portanto, não tenho problemas com a definição da função hash. Quanto mais eu pensava sobre isso, mais percebia que não encontrava nenhuma razão para usar um std::map
over a std::unordered_map
no caso de chaves com tipos simples - observei as interfaces e não encontrei nenhuma diferenças significativas que impactariam meu código.
Daí a pergunta: existe alguma razão real para usar std::map
mais std::unordered_map
no caso de tipos simples, como int
e std::string
?
Estou perguntando do ponto de vista estritamente de programação - sei que não é totalmente considerado padrão e que pode causar problemas com a portabilidade.
Além disso, espero que uma das respostas corretas possa ser "é mais eficiente para conjuntos menores de dados" devido a uma sobrecarga menor (isso é verdade?) - portanto, gostaria de restringir a pergunta a casos em que a quantidade de chaves não é trivial (> 1 024).
Edit: duh, esqueci o óbvio (obrigado GMan!) - sim, os mapas estão ordenados, é claro - eu sei disso e estou procurando por outros motivos.
fonte
Respostas:
Não esqueça que
map
mantém seus elementos ordenados. Se você não pode desistir disso, obviamente não pode usarunordered_map
.Outra coisa a ter em mente é que
unordered_map
geralmente usa mais memória.map
apenas possui alguns indicadores de manutenção da casa e memória para cada objeto. Por outro lado,unordered_map
possui uma grande matriz (pode ser bastante grande em algumas implementações) e, em seguida, memória adicional para cada objeto. Se você precisa estar ciente da memória,map
deve provar melhor, porque falta uma grande variedade.Então, se você precisar de pura recuperação de pesquisa, eu diria que
unordered_map
é o caminho a percorrer. Mas sempre existem trocas e, se você não pode pagar, não pode usá-lo.Apenas por experiência pessoal, encontrei uma enorme melhoria no desempenho (medida, é claro) ao usar em
unordered_map
vez demap
em uma tabela de consulta de entidade principal.Por outro lado, achei muito mais lento inserir e remover repetidamente elementos. É ótimo para uma coleção relativamente estática de elementos, mas se você estiver fazendo muitas inserções e exclusões, o hashing + bucketing parece somar. (Observe, isso ocorreu em várias iterações.)
fonte
unordered_map
e reserva isso no início - você ainda paga uma multa de muitas inserções? Digamos, você está inserindo apenas uma vez quando criou a tabela de pesquisa - e depois apenas lê a partir dela.Se você quiser comparar a velocidade de suas implementações
std::map
e de suasstd::unordered_map
implementações, use o projeto sparsehash do Google, que possui um programa time_hash_map para cronometrá-las. Por exemplo, com o gcc 4.4.2 em um sistema Linux x86_64fonte
Eu ecoaria aproximadamente o mesmo ponto que GMan fez: dependendo do tipo de uso,
std::map
pode ser (e geralmente é) mais rápido questd::tr1::unordered_map
(usando a implementação incluída no VS 2008 SP1).Existem alguns fatores complicadores a serem lembrados. Por exemplo, em
std::map
, você está comparando chaves, o que significa que você só olha o suficiente no início de uma chave para distinguir entre os sub-ramos direito e esquerdo da árvore. Na minha experiência, quase a única vez em que você olha para uma chave inteira é se estiver usando algo como int que possa comparar em uma única instrução. Com um tipo de chave mais típico, como std :: string, você costuma comparar apenas alguns caracteres.Uma função de hash decente, por outro lado, sempre olha para a chave inteira . IOW, mesmo que a pesquisa da tabela seja de complexidade constante, o hash em si tem uma complexidade aproximadamente linear (embora no comprimento da chave, não no número de itens). Com cadeias longas como chaves, um
std::map
pode concluir uma pesquisa antesunordered_map
mesmo de iniciar sua pesquisa.Segundo, embora existam vários métodos de redimensionar tabelas de hash, a maioria deles é bem lenta - ao ponto de que, a menos que as pesquisas sejam consideravelmente mais frequentes do que inserções e exclusões, std :: map geralmente será mais rápido que
std::unordered_map
.Obviamente, como mencionei no comentário da sua pergunta anterior, você também pode usar uma tabela de árvores. Isso tem vantagens e desvantagens. Por um lado, limita o pior caso ao de uma árvore. Também permite inserção e exclusão rápidas, porque (pelo menos quando eu fiz isso) eu usei um tamanho fixo de tabela. A eliminação de todo o redimensionamento de tabelas permite manter sua tabela de hash muito mais simples e geralmente mais rápida.
Outro ponto: os requisitos para o hash e os mapas baseados em árvore são diferentes. O hash obviamente requer uma função de hash e uma comparação de igualdade, onde os mapas ordenados requerem uma comparação menor que a. É claro que o híbrido que mencionei requer ambos. Obviamente, para o caso comum de usar uma string como chave, isso não é realmente um problema, mas alguns tipos de chaves são mais adequados para pedidos do que para hash (ou vice-versa).
fonte
dynamic hashing
técnica, que consiste em ter um período de transição em que cada vez que você insere um item, você também refazk
outros itens. Claro, isso significa que durante a transição você tem que procurar 2 mesas diferentes ...unordered_map
necessário confirmar uma correspondência de hash com uma comparação completa; portanto, tudo depende de quais partes do processo de pesquisa você está contrastando.Fiquei intrigado com a resposta de @Jerry Coffin, que sugeriu que o mapa ordenado exibisse aumentos de desempenho em seqüências longas, depois de alguma experimentação (que pode ser baixada do pastebin ), descobri que isso parece válido apenas para coleções de seqüências aleatórias, quando o mapa é inicializado com um dicionário classificado (que contém palavras com quantidades consideráveis de sobreposição de prefixos), essa regra é quebrada, provavelmente devido ao aumento da profundidade da árvore necessária para recuperar o valor. Os resultados são mostrados abaixo, a primeira coluna numérica é o tempo de inserção e o segundo é o tempo de busca.
fonte
std::map
geralmente supera o desempenhostd::unordered_map
, especialmente para teclas inteiras, mas ~ 100 teclas parece que perde a vantagem estd::unordered_map
começa a ganhar. Inserir uma sequência já ordenada em umastd::map
é muito ruim, você terá o pior cenário possível (O (N)).Gostaria apenas de salientar que ... existem muitos tipos de
unordered_map
s.Consulte o artigo da Wikipedia no mapa de hash. Dependendo de qual implementação foi usada, as características em termos de pesquisa, inserção e exclusão podem variar bastante.
E é isso que mais me preocupa com a adição do
unordered_map
STL: eles terão que escolher uma implementação específica, pois duvido quePolicy
seguirão adiante e, portanto, ficaremos presos a uma implementação para o uso médio e nada para os outros casos ...Por exemplo, alguns mapas de hash têm reformulação linear, onde, em vez de reformular todo o mapa de hash de uma só vez, uma parte é reformulada em cada inserção, o que ajuda a amortizar o custo.
Outro exemplo: alguns mapas de hash usam uma lista simples de nós para um bucket, outros usam um mapa, outros não usam nós, mas encontram o slot mais próximo e, por último, alguns usam uma lista de nós, mas reordenam para que o último elemento acessado está na frente (como uma coisa de cache).
Então, no momento, eu prefiro o
std::map
ou talvez umloki::AssocVector
(para conjuntos de dados congelados).Não me entenda mal, eu gostaria de usar o
std::unordered_map
e eu posso no futuro, mas é difícil "confiar" na portabilidade de um contêiner quando você pensa em todas as maneiras de implementá-lo e nos vários desempenhos resultantes disto.fonte
Diferenças significativas que realmente não foram adequadamente mencionadas aqui:
map
mantém os iteradores para todos os elementos estáveis, no C ++ 17 você pode até mover elementos de ummap
para o outro sem invalidar os iteradores para eles (e, se implementado corretamente, sem qualquer alocação em potencial).map
horários para operações únicas são geralmente mais consistentes, pois nunca precisam de grandes alocações.unordered_map
o usostd::hash
conforme implementado no libstdc ++ é vulnerável ao DoS se alimentado com entrada não confiável (ele usa o MurmurHash2 com uma semente constante - não que a propagação realmente ajude, consulte https://emboss.github.io/blog/2012/12/14/ quebrando-sopro-hash-inundando-dos-recarregado / ).fonte
As tabelas de hash têm constantes mais altas que as implementações comuns de mapas, que se tornam significativas para pequenos contêineres. O tamanho máximo é 10, 100 ou talvez 1.000 ou mais? As constantes são as mesmas de sempre, mas O (log n) está próximo de O (k). (Lembre-se de que a complexidade logarítmica ainda é muito boa.)
O que faz uma boa função de hash depende das características dos seus dados; portanto, se eu não pretendo olhar para uma função de hash personalizada (mas certamente posso mudar de idéia mais tarde, e facilmente, já que digitei muito perto de tudo) e mesmo que os padrões sejam escolhidos para executar decentemente para muitas fontes de dados, encontro as instruções natureza do mapa para ser uma ajuda suficiente inicialmente, que eu ainda padronizo para mapear, em vez de uma tabela de hash nesse caso.
Além disso, dessa forma, você não precisa nem pensar em escrever uma função de hash para outros tipos (geralmente UDT) e apenas escrever op <(que você deseja de qualquer maneira).
fonte
map
e uma deunordered_map
, com certa plataforma e determinado tamanho de cache, e fazer uma análise complexa. : PRazões foram dadas em outras respostas; aqui está outro.
As operações std :: map (árvore binária balanceada) são amortizadas O (log n) e, na pior das hipóteses, O (log n). As operações std :: unordered_map (tabela de hash) são amortizadas O (1) e, na pior das hipóteses, O (n).
Como isso ocorre na prática é que a tabela de hash "soluça" de vez em quando com uma operação O (n), que pode ou não ser algo que seu aplicativo pode tolerar. Se não puder tolerá-lo, você preferiria std :: map sobre std :: unordered_map.
fonte
Sumário
Assumindo que a encomenda não é importante:
std::unordered_map
std::map
. Isso ocorre porque as leituras sãoO(log n)
.std::map
uma boa opção.std::unordered_map
.Contexto histórico
Na maioria dos idiomas, o mapa não ordenado (também conhecido como dicionários baseados em hash) é o mapa padrão; no entanto, em C ++, você recebe o mapa ordenado como mapa padrão. Como isso aconteceu? Algumas pessoas assumem erroneamente que o comitê C ++ tomou essa decisão com sua sabedoria única, mas a verdade é infelizmente mais feia do que isso.
Acredita- se que o C ++ tenha acabado com o mapa ordenado como padrão, porque não há muitos parâmetros sobre como eles podem ser implementados. Por outro lado, as implementações baseadas em hash têm muito o que falar. Portanto, para evitar bloqueios na padronização, eles apenas se deram bem com o mapa ordenado. Por volta de 2005, muitos idiomas já tinham boas implementações de implementação baseada em hash e, portanto, era mais fácil para o comitê aceitar novas
std::unordered_map
. Em um mundo perfeito,std::map
teria sido desordenado e teríamosstd::ordered_map
como tipo separado.atuação
Abaixo dois gráficos devem falar por si ( fonte ):
fonte
Eu fiz recentemente um teste que faz 50000 mesclar e classificar. Isso significa que, se as chaves da string forem as mesmas, mescle a string de bytes. E a saída final deve ser classificada. Portanto, isso inclui uma pesquisa para cada inserção.
Para a
map
implementação, são necessários 200 ms para concluir o trabalho. Para ounordered_map
+map
, são necessários 70 ms paraunordered_map
inserção e 80 ms paramap
inserção. Portanto, a implementação híbrida é 50 ms mais rápida.Devemos pensar duas vezes antes de usar o
map
. Se você só precisa classificar os dados no resultado final do seu programa, uma solução híbrida pode ser melhor.fonte
Pequena adição a todos os itens acima:
Melhor uso
map
, quando você precisar obter elementos por intervalo, pois eles são classificados e você pode simplesmente iterá-los de um limite para outro.fonte
De: http://www.cplusplus.com/reference/map/map/
"Internamente, os elementos em um mapa são sempre classificados por sua chave, seguindo um critério específico de ordem fraca estrita, indicado por seu objeto de comparação interna (do tipo Compare).
os contêineres de mapa geralmente são mais lentos que os contêineres unordered_map para acessar elementos individuais por sua chave, mas permitem a iteração direta em subconjuntos com base em seu pedido ".
fonte