Una charla reciente sobre unordered_map
C ++ me hizo darme cuenta de que debería usarlo unordered_map
para la mayoría de los casos en los que lo usaba map
antes, debido a la eficiencia de la búsqueda ( O amortizado (1) versus O (log n) ). La mayoría de las veces utilizo un mapa, utilizo cualquiera int
o std::string
como tipo de clave; por lo tanto, no tengo problemas con la definición de la función hash. Cuanto más lo pensaba, más me daba cuenta de que no encontraba ninguna razón para usar un std::map
over a std::unordered_map
en el caso de teclas con tipos simples: eché un vistazo a las interfaces y no encontré ninguna diferencias significativas que afectarían mi código.
De ahí la pregunta: ¿hay alguna razón real para utilizar std::map
sobre std::unordered_map
en el caso de los tipos simples como int
e std::string
?
Lo pregunto desde un punto de vista estrictamente de programación: sé que no se considera completamente estándar y que puede plantear problemas con la portabilidad.
Además, espero que una de las respuestas correctas sea "es más eficiente para conjuntos de datos más pequeños" debido a una sobrecarga menor (¿es eso cierto?), Por lo tanto, me gustaría restringir la pregunta a casos donde la cantidad de las claves no son triviales (> 1 024).
Editar: duh, olvidé lo obvio (¡gracias GMan!), Sí, los mapas están ordenados, por supuesto, lo sé y estoy buscando otras razones.
fuente
Respuestas:
No olvides que
map
mantiene sus elementos ordenados. Si no puedes renunciar a eso, obviamente no puedes usarlounordered_map
.Algo más a tener en cuenta es que
unordered_map
generalmente usa más memoria.map
solo tiene algunos indicadores de mantenimiento y memoria para cada objeto. Por el contrario,unordered_map
tiene una gran matriz (estas pueden ser bastante grandes en algunas implementaciones), y luego memoria adicional para cada objeto. Si necesita tener en cuenta la memoria,map
debería ser mejor, ya que carece de la gran matriz.Entonces, si necesita una búsqueda de recuperación pura, diría que
unordered_map
es el camino a seguir. Pero siempre hay compensaciones, y si no puede pagarlas, entonces no puede usarlas.Solo por experiencia personal, encontré una enorme mejora en el rendimiento (medido, por supuesto) cuando se usa en
unordered_map
lugar demap
en una tabla de búsqueda de entidad principal.Por otro lado, descubrí que era mucho más lento insertar y eliminar elementos repetidamente. Es ideal para una colección de elementos relativamente estática, pero si está haciendo toneladas de inserciones y eliminaciones, el hashing + bucketing parece sumar. (Tenga en cuenta que esto fue durante muchas iteraciones).
fuente
unordered_map
reserva y la reserva al principio, ¿todavía paga una multa de muchas inserciones? Digamos que solo está insertando una vez cuando creó la tabla de búsqueda, y luego solo leyó de ella.Si desea comparar la velocidad de sus
std::map
estd::unordered_map
implementaciones, se puede usar de Google sparsehash proyecto, que tiene un programa time_hash_map en cuando ellos. Por ejemplo, con gcc 4.4.2 en un sistema Linux x86_64fuente
Hago eco aproximadamente del mismo punto que GMan hizo: dependiendo del tipo de uso,
std::map
puede ser (y a menudo es) más rápido questd::tr1::unordered_map
(usando la implementación incluida en VS 2008 SP1).Hay algunos factores complicados a tener en cuenta. Por ejemplo, en
std::map
, estás comparando claves, lo que significa que solo miras lo suficiente el comienzo de una clave para distinguir entre las ramas secundarias derecha e izquierda del árbol. En mi experiencia, casi la única vez que mira una clave completa es si está usando algo como int que puede comparar en una sola instrucción. Con un tipo de clave más típico como std :: string, a menudo solo se comparan unos pocos caracteres.Una función hash decente, por el contrario, siempre mira la clave completa . IOW, incluso si la búsqueda de la tabla es una complejidad constante, el hash en sí tiene una complejidad aproximadamente lineal (aunque en la longitud de la clave, no en el número de elementos). Con cadenas largas como llaves, una
std::map
podría terminar una búsqueda antes de unaunordered_map
siquiera comenzar su búsqueda.En segundo lugar, si bien existen varios métodos para cambiar el tamaño de las tablas hash, la mayoría de ellos son bastante lentos, hasta el punto de que, a menos que las búsquedas sean considerablemente más frecuentes que las inserciones y eliminaciones, std :: map a menudo será más rápido que
std::unordered_map
.Por supuesto, como mencioné en el comentario sobre su pregunta anterior, también puede usar una tabla de árboles. Esto tiene ventajas y desventajas. Por un lado, limita el peor de los casos al de un árbol. También permite una inserción y eliminación rápidas, porque (al menos cuando lo hice) he usado una tabla de tamaño fijo. Eliminar todo el cambio de tamaño de la tabla le permite mantener su tabla hash mucho más simple y generalmente más rápida.
Otro punto: los requisitos para el hash y los mapas basados en árboles son diferentes. Hashing obviamente requiere una función hash y una comparación de igualdad, donde los mapas ordenados requieren una comparación menor. Por supuesto, el híbrido que mencioné requiere ambos. Por supuesto, para el caso común de usar una cadena como clave, esto no es realmente un problema, pero algunos tipos de claves se adaptan mejor a la ordenación que el hash (o viceversa).
fuente
dynamic hashing
técnicas, que consisten en tener un período de transición en el que cada vez que inserta un elemento, también vuelve a mostrark
otros elementos. Por supuesto, significa que durante la transición tienes que buscar 2 tablas diferentes ...unordered_map
debe confirmar una coincidencia hash con una comparación completa, por lo que todo depende de las partes del proceso de búsqueda que esté contrastando.Me intrigó la respuesta de @Jerry Coffin, que sugirió que el mapa ordenado exhibiría aumentos de rendimiento en cadenas largas, después de un poco de experimentación (que se puede descargar desde pastebin ), descubrí que esto solo parece ser cierto para las colecciones de cadenas aleatorias, cuando el mapa se inicializa con un diccionario ordenado (que contiene palabras con cantidades considerables de superposición de prefijos), esta regla se rompe, presumiblemente debido a la mayor profundidad del árbol necesaria para recuperar el valor. Los resultados se muestran a continuación, la columna del primer número es el tiempo de inserción, el segundo es el tiempo de recuperación.
fuente
std::map
generalmente superastd::unordered_map
, especialmente para las teclas enteras, pero ~ 100 teclas parece que pierde su ventaja ystd::unordered_map
comienza a ganar. Insertar una secuencia ya ordenada en unastd::map
es muy mala, obtendrá el peor de los casos (O (N)).Solo señalaría que ... hay muchos tipos de
unordered_map
s.Busque el artículo de Wikipedia en el mapa hash. Dependiendo de la implementación utilizada, las características en términos de búsqueda, inserción y eliminación pueden variar bastante significativamente.
Y eso es lo que más me preocupa con la incorporación de
unordered_map
STL: tendrán que elegir una implementación particular, ya que dudo que sigan adelantePolicy
, por lo que nos quedaremos atrapados con una implementación para el uso promedio y nada para los otros casos ...Por ejemplo, algunos mapas hash tienen rehashing lineal, donde en lugar de volver a rehacer todo el mapa hash a la vez, se repite una porción en cada inserción, lo que ayuda a amortizar el costo.
Otro ejemplo: algunos mapas hash usan una lista simple de nodos para un cubo, otros usan un mapa, otros no usan nodos pero encuentran la ranura más cercana y, por último, algunos usarán una lista de nodos pero la reordenarán para que el último elemento accedido está en la parte delantera (como una cosa de almacenamiento en caché).
Entonces, en este momento, tiendo a preferir el
std::map
o quizás unloki::AssocVector
(para conjuntos de datos congelados).No me malinterpreten, me gustaría usarlo
std::unordered_map
y podría hacerlo en el futuro, pero es difícil "confiar" en la portabilidad de dicho contenedor cuando se piensa en todas las formas de implementarlo y las diversas actuaciones que resultan de esta.fuente
Diferencias significativas que realmente no se han mencionado adecuadamente aquí:
map
mantiene los iteradores a todos los elementos estables, en C ++ 17 incluso puede mover elementos de unomap
a otro sin invalidar los iteradores (y si se implementa correctamente sin ninguna asignación potencial).map
Los tiempos para operaciones individuales son típicamente más consistentes ya que nunca necesitan grandes asignaciones.unordered_map
el usostd::hash
implementado en libstdc ++ es vulnerable a DoS si se alimenta con una entrada no confiable (usa MurmurHash2 con una semilla constante; no es que la siembra realmente ayude, consulte https://emboss.github.io/blog/2012/12/14/ romper-murmullo-hash-flooding-dos-reloaded / ).fuente
Las tablas hash tienen constantes más altas que las implementaciones de mapas comunes, que se vuelven significativas para los contenedores pequeños. ¿El tamaño máximo es 10, 100 o tal vez incluso 1,000 o más? Las constantes son las mismas de siempre, pero O (log n) está cerca de O (k). (Recuerde que la complejidad logarítmica sigue siendo realmente buena).
Lo que hace que una buena función hash dependa de las características de sus datos; así que si no planeo mirar una función hash personalizada (pero ciertamente puedo cambiar de opinión más tarde, y fácilmente ya que escribo bastante cerca de todo) y aunque los valores predeterminados se eligen para funcionar decentemente para muchas fuentes de datos, encuentro el pedido la naturaleza del mapa es suficiente para ayudar inicialmente, que todavía prefiero asignar en lugar de una tabla hash en ese caso.
Además, de esa manera ni siquiera tiene que pensar en escribir una función hash para otros tipos (generalmente UDT), y simplemente escribir op <(que de todos modos quiere).
fuente
map
unaunordered_map
, con cierta plataforma y cierto tamaño de caché, y hacer un análisis complejo. : PSe han dado razones en otras respuestas; aquí está otro.
Las operaciones std :: map (árbol binario balanceado) se amortizan O (log n) y el peor de los casos O (log n). Las operaciones std :: unordered_map (tabla hash) se amortizan O (1) y el peor de los casos O (n).
Lo que sucede en la práctica es que la tabla hash "tiene hipo" de vez en cuando con una operación O (n), que puede o no ser algo que su aplicación pueda tolerar. Si no puede tolerarlo, preferiría std :: map sobre std :: unordered_map.
fuente
Resumen
Asumir que el pedido no es importante:
std::unordered_map
std::map
. Esto es porque las lecturas sonO(log n)
.std::map
una buena opción.std::unordered_map
.Contexto histórico
En la mayoría de los idiomas, el mapa no ordenado (también conocido como diccionarios basados en hash) es el mapa predeterminado; sin embargo, en C ++ se obtiene el mapa ordenado como mapa predeterminado. ¿Cómo pasó eso? Algunas personas suponen erróneamente que el comité de C ++ tomó esta decisión con su sabiduría única, pero desafortunadamente la verdad es más fea que eso.
Se cree ampliamente que C ++ terminó con el mapa ordenado como predeterminado porque no hay demasiados parámetros sobre cómo se pueden implementar. Por otro lado, las implementaciones basadas en hash tienen toneladas de cosas de qué hablar. Entonces, para evitar bloqueos en la estandarización, simplemente se llevaron bien con el mapa ordenado. Alrededor de 2005, muchos idiomas ya tenían buenas implementaciones de implementación basada en hash, por lo que fue más fácil para el comité aceptar nuevas
std::unordered_map
. En un mundo perfecto,std::map
habría sido desordenado y tendríamosstd::ordered_map
como tipo separado.Actuación
A continuación, dos gráficos deben hablar por sí mismos ( fuente ):
fuente
Hice una prueba recientemente que hace 50000 fusionar y ordenar. Eso significa que si las teclas de cadena son las mismas, combine la cadena de bytes. Y el resultado final debe ser ordenado. Entonces esto incluye una búsqueda para cada inserción.
Para la
map
implementación, se requieren 200 ms para finalizar el trabajo. Para elunordered_map
+map
, se requieren 70 ms para launordered_map
inserción y 80 ms para lamap
inserción. Entonces, la implementación híbrida es 50 ms más rápida.Deberíamos pensarlo dos veces antes de usar el
map
. Si solo necesita ordenar los datos en el resultado final de su programa, una solución híbrida puede ser mejor.fuente
Pequeña adición a todo lo anterior:
Mejor uso
map
, cuando necesita obtener elementos por rango, ya que están ordenados y puede iterar sobre ellos de un límite a otro.fuente
De: http://www.cplusplus.com/reference/map/map/
"Internamente, los elementos en un mapa siempre se ordenan por su clave siguiendo un criterio de orden débil estricto específico indicado por su objeto de comparación interno (de tipo Comparar).
los contenedores de mapas son generalmente más lentos que los contenedores de mapas desordenados para acceder a elementos individuales por su clave, pero permiten la iteración directa en subconjuntos según su orden ".
fuente