java - examples - hashmap vs hashtable
Rendimiento ConcurrentHashmap vs HashMap (7)
¿Cómo se compara el rendimiento de ConcurrentHashMap con HashMap, especialmente con la operación .get ()? Estoy especialmente interesado en el caso de pocos artículos, en el rango entre quizás 0-5000.
¿Hay alguna razón para no usar ConcurrentHashMap en lugar de HashMap?
(Sé que los valores nulos no están permitidos)
Actualizar
solo para aclarar, obviamente, el rendimiento en caso de acceso concurrente real sufrirá, pero ¿cómo se compara el rendimiento en caso de que no haya acceso simultáneo?
¿Qué respuesta esperas aquí?
Obviamente dependerá de la cantidad de lecturas que se realicen al mismo tiempo que las escrituras y de cuánto tiempo debe "bloquearse" un mapa normal en una operación de escritura en su aplicación (y si se usaría el método putIfAbsent
en ConcurrentMap
) . Cualquier punto de referencia va a ser en gran medida sin sentido.
El hashmap estándar no proporciona protección de concurrencia, mientras que el hashmap concurrente sí lo hace. Antes de que estuviera disponible, podías ajustar el hashmap para obtener acceso seguro a las hebras pero esto era un bloqueo de grano grueso y significaba que todos los accesos simultáneos se serializaban, lo que realmente podría afectar el rendimiento.
El hashmap concurrente usa bloqueo de bloqueo y solo bloquea elementos afectados por un bloqueo particular. Si está ejecutando en un vm moderno, como hotspot, la vm intentará utilizar el bloqueo de sesgo, el desplazamiento y ellision si es posible, por lo que solo pagará la penalización de los bloqueos cuando realmente lo necesite.
En resumen, si su mapa va a ser accesado por hilos concurrentes y necesita garantizar una vista consistente de su estado, use el hashmap concurrente.
En el caso de una tabla hash de 1000 elementos que usa 10 bloqueos para la tabla completa, se ahorra casi la mitad del tiempo cuando 10000 hilos se están insertando y 10000 hilos se están borrando de ella.
La interesante diferencia de tiempo de ejecución está here
Siempre use la estructura de datos concurrente. excepto cuando la desventaja de las rayas (mencionado a continuación) se convierte en una operación frecuente. En ese caso, tendrá que adquirir todos los bloqueos? Leí que la mejor manera de hacerlo es recurriendo.
Las divisiones de bloqueo son útiles cuando hay una forma de romper un bloqueo de contención alto en bloqueos múltiples sin comprometer la integridad de los datos. Si esto es posible o no, debe pensar un poco y no siempre es el caso. La estructura de datos también es el factor que contribuye a la decisión. Entonces, si usamos una matriz grande para implementar una tabla hash, usar un único bloqueo para toda la tabla hash para sincronizarla conducirá a que los hilos accedan secuencialmente a la estructura de datos. Si esta es la misma ubicación en la tabla hash, entonces es necesario pero, ¿y si están accediendo a los dos extremos de la tabla?
El lado negativo de las franjas de bloqueo es que es difícil obtener el estado de la estructura de datos que se ve afectada por las bandas. En el ejemplo, el tamaño de la tabla, o tratar de enumerar / enumerar toda la tabla puede ser engorroso, ya que necesitamos adquirir todos los bloqueos seccionados.
La seguridad de subprocesos es una pregunta compleja. Si desea que un objeto sea seguro, hágalo conscientemente y documente esa elección. Las personas que usan tu clase te lo agradecerán si es seguro para la ejecución de subprocesos cuando simplifica su uso, pero te maldecirán si un objeto que una vez fue seguro para subprocesos no lo es en una versión futura. La seguridad del hilo, aunque es realmente agradable, ¡no es solo para Navidad!
Entonces ahora a tu pregunta:
ConcurrentHashMap (al menos en la implementación actual de Sun ) funciona dividiendo el mapa subyacente en una cantidad de segmentos diferentes. Obtener un elemento no requiere ningún bloqueo per se, pero sí utiliza operaciones atómicas / volátiles, lo que implica una barrera de memoria (potencialmente muy costosa e interfiere con otras optimizaciones posibles).
Incluso si todos los gastos generales de las operaciones atómicas pueden ser eliminados por el compilador de JIT en un caso de subproceso único, aún existe la sobrecarga de decidir cuál de los cubos debe mirar, sin embargo, este es un cálculo relativamente rápido, pero no obstante, es imposible de eliminar
En cuanto a decidir qué implementación usar, la elección es probablemente simple.
Si se trata de un campo estático, es casi seguro que desee utilizar ConcurrentHashMap, a menos que las pruebas muestren que este es un verdadero asesino de rendimiento. Su clase tiene expectativas de seguridad de hilos diferentes de las instancias de esa clase.
Si se trata de una variable local, entonces es probable que un HashMap sea suficiente, a menos que sepa que las referencias al objeto pueden filtrarse a otro hilo. Al codificar en la interfaz de Mapa, te permites cambiarlo fácilmente más tarde si descubres un problema.
Si se trata de un campo de instancia, y la clase no se ha diseñado para que sea segura para subprocesos, documentarla como no segura para subprocesos y usar un HashMap.
Si sabe que este campo de instancia es la única razón por la cual la clase no es segura para subprocesos y está dispuesta a vivir con las restricciones que implica la prometedora seguridad de subprocesos, entonces use ConcurrentHashMap, a menos que las pruebas muestren implicaciones de rendimiento significativas. En ese caso, podría considerar permitir que un usuario de la clase elija de algún modo una versión segura para subprocesos del hilo, quizás utilizando un método de fábrica diferente.
En cualquier caso, documente la clase como segura para subprocesos (o con seguridad de subprocesos) para que las personas que usan su clase sepan que pueden usar objetos en varios subprocesos, y las personas que editan su clase saben que deben mantener la seguridad de subprocesos en el futuro.
Le recomendaría que lo mida, ya que (por una razón) puede haber cierta dependencia de la distribución hash de los objetos particulares que está almacenando.
No está claro cuál es tu significado. Si necesita seguridad de subprocesos, casi no tiene elección, solo ConcurrentHashMap. Y definitivamente tiene penalizaciones de rendimiento / memoria en la llamada get (): acceso a variables volátiles y bloqueo si no tiene suerte.
Me sorprendió mucho encontrar este tema tan viejo y, sin embargo, nadie me ha proporcionado ninguna prueba sobre el caso. Usando ScalaMeter
, he creado pruebas de add
, get
y remove
tanto para HashMap
como para ConcurrentHashMap
en dos escenarios:
- utilizando un solo hilo
- usando tantos hilos como tengo núcleos disponibles. Tenga en cuenta que debido a que
HashMap
no es seguro para subprocesos, simplemente creéHashMap
por separado para cada subproceso, pero usé uno,ConcurrentHashMap
compartido.
El código está disponible en mi repositorio .
Los resultados son los siguientes:
- El eje X (tamaño) presenta el número de elementos escritos en el mapa (s)
- El eje Y (valor) presenta el tiempo en milisegundos
El resumen
Si desea operar sus datos lo más rápido posible, use todos los hilos disponibles. Eso parece obvio, cada hilo tiene 1/13 del trabajo completo por hacer.
Si elige un solo acceso a subprocesos, use
HashMap
, simplemente es más rápido. Para el métodoadd
, es incluso más eficiente que 3 veces. Sologet
es más rápido enConcurrentHashMap
, pero no mucho.Cuando se opera en
ConcurrentHashMap
con muchos subprocesos, es igualmente efectivo operar enHashMaps
separados para cada subproceso. Por lo tanto, no es necesario dividir sus datos en diferentes estructuras.
En resumen, el rendimiento de ConcurrentHashMap
es peor cuando se utiliza con un solo hilo, pero agregar más hilos para hacer el trabajo acelerará definitivamente el proceso.
Plataforma de prueba
AMD FX6100, 16GB Ram
Xubuntu 16.04, Oracle JDK 8 actualización 91, Scala 2.11.8