web-dev-qa-db-fra.com

Performance ConcurrentHashmap vs HashMap

Quelle est la performance de ConcurrentHashMap par rapport à HashMap, en particulier l'opération .get () (je suis particulièrement intéressé pour le cas de quelques éléments seulement, dans la plage entre peut-être 0-5000)?

Y a-t-il une raison de ne pas utiliser ConcurrentHashMap au lieu de HashMap?

(Je sais que les valeurs nulles ne sont pas autorisées)

Mise à jour

juste pour clarifier, évidemment les performances en cas d'accès simultané réel en souffriront, mais comment compare les performances en cas d'absence d'accès simultané?

58
Mauli

J'ai été vraiment surpris de constater que ce sujet était si ancien et pourtant personne n'a encore fourni de tests concernant l'affaire. En utilisant ScalaMeter, j'ai créé des tests de add, get et remove pour les deux HashMap et ConcurrentHashMap dans deux scénarios :

  1. en utilisant un seul fil
  2. en utilisant autant de threads que j'ai de cœurs disponibles. Notez que parce que HashMap n'est pas adapté aux threads, j'ai simplement créé des HashMap séparés pour chaque thread, mais j'en ai utilisé un, partagé ConcurrentHashMap.

Le code est disponible sur mon dépôt .

Les résultats sont les suivants:

  • L'axe X (taille) présente le nombre d'éléments écrits sur la ou les cartes
  • L'axe Y (valeur) présente le temps en millisecondes

Add method Get method Remove method

Le résumé

  • Si vous souhaitez exploiter vos données le plus rapidement possible, utilisez tous les threads disponibles. Cela semble évident, chaque thread a 1/nième du travail complet à faire.

  • Si vous choisissez un accès à un seul thread, utilisez HashMap, c'est tout simplement plus rapide. Pour la méthode add, elle est même jusqu'à 3 fois plus efficace. Seul get est plus rapide sur ConcurrentHashMap, mais pas beaucoup.

  • Lorsque vous travaillez sur ConcurrentHashMap avec plusieurs threads, il est tout aussi efficace de fonctionner sur des HashMaps séparés pour chaque thread. Il n'est donc pas nécessaire de partitionner vos données dans différentes structures.

Pour résumer, les performances de ConcurrentHashMap sont moins bonnes lorsque vous utilisez un seul thread, mais l'ajout de plus de threads pour effectuer le travail accélérera certainement le processus.

Plateforme de test

AMD FX6100, 16 Go de RAM
Xubuntu 16.04, mise à jour 91 d'Oracle JDK 8, Scala 2.11.8

62
Atais

La sécurité des fils est une question complexe. Si vous souhaitez sécuriser un thread d'objet, faites-le consciemment et documentez ce choix. Les personnes qui utilisent votre classe vous remercieront si elle est sécurisée pour les threads lorsque cela simplifie leur utilisation, mais elles vous maudiront si un objet qui était autrefois sûr pour les threads ne le sera plus dans une future version. La sécurité des fils, bien que vraiment agréable, n'est pas seulement pour Noël!

Passons maintenant à votre question:

ConcurrentHashMap (au moins dans implémentation actuelle de Sun ) fonctionne en divisant la carte sous-jacente en un certain nombre de compartiments distincts. Obtenir un élément ne nécessite pas de verrouillage en soi, mais il utilise des opérations atomiques/volatiles, ce qui implique une barrière de mémoire (potentiellement très coûteuse et interférant avec d'autres optimisations possibles).

Même si tous les frais généraux des opérations atomiques peuvent être éliminés par le compilateur JIT dans un cas à un seul thread, il y a toujours le temps de décider dans lequel des compartiments regarder - certes, c'est un calcul relativement rapide, mais néanmoins, il est impossible à éliminer.

Quant à décider quelle implémentation utiliser, le choix est probablement simple.

S'il s'agit d'un champ statique, vous voudrez certainement utiliser ConcurrentHashMap, à moins que les tests montrent qu'il s'agit d'un véritable tueur de performances. Votre classe a des attentes en matière de sécurité des threads différentes des instances de cette classe.

S'il s'agit d'une variable locale, il est probable qu'un HashMap soit suffisant - sauf si vous savez que les références à l'objet peuvent s'échapper vers un autre thread. En codant sur l'interface Carte, vous vous permettez de le modifier facilement plus tard si vous découvrez un problème.

S'il s'agit d'un champ d'instance et que la classe n'a pas été conçue pour être thread-safe, documentez-la comme non thread-safe et utilisez un HashMap.

Si vous savez que ce champ d'instance est la seule raison pour laquelle la classe n'est pas sûre pour les threads et que vous êtes prêt à respecter les restrictions qu'implique la sécurité des threads prometteuses, utilisez ConcurrentHashMap, sauf si les tests montrent des implications de performances significatives. Dans ce cas, vous pourriez envisager d'autoriser un utilisateur de la classe à choisir une version thread-safe de l'objet d'une manière ou d'une autre, peut-être en utilisant une méthode d'usine différente.

Dans les deux cas, documentez la classe comme étant thread-safe (ou conditionnellement thread-safe) afin que les personnes qui utilisent votre classe sachent qu'elles peuvent utiliser des objets sur plusieurs threads, et les personnes qui modifient votre classe savent qu'elles doivent maintenir la sécurité des threads à l'avenir.

86
Bill Michell

Je vous recommande de le mesurer, car (pour une raison) il y a peut une certaine dépendance à la distribution de hachage des objets particuliers que vous stockez.

3
Brian Agnew

Le hashmap standard n'offre aucune protection de concurrence, contrairement au hashmap simultané. Avant d'être disponible, vous pouviez envelopper la table de hachage pour obtenir un accès sécurisé aux threads, mais c'était un verrouillage à grain grossier et signifiait que tous les accès simultanés étaient sérialisés, ce qui pouvait vraiment avoir un impact sur les performances.

La table de hachage simultanée utilise la suppression des verrous et ne verrouille que les éléments affectés par un verrou particulier. Si vous utilisez un vm moderne tel qu'un hotspot, le vm essaiera d'utiliser la polarisation, la granularité et l'ellision des verrous si possible afin de ne payer la pénalité pour les verrous que lorsque vous en avez réellement besoin.

En résumé, si votre carte va être accédée par des threads simultanés et que vous devez garantir une vue cohérente de son état, utilisez la table de hachage simultanée.

3
Robert Christie

Dans le cas d'une table de hachage de 1000 éléments, l'utilisation de 10 verrous pour la table entière permet d'économiser près de la moitié du temps lorsque 10000 threads sont insérés et 10000 threads en sont supprimés.

La différence de temps d'exécution intéressante est ici

Utilisez toujours la structure de données simultanées. sauf lorsque l'inconvénient du striping (mentionné ci-dessous) devient une opération fréquente. Dans ce cas, vous devrez acquérir toutes les serrures? J'ai lu que la meilleure façon de procéder est la récursivité.

L'entrelacement des verrous est utile lorsqu'il existe un moyen de briser un verrou à conflit élevé en plusieurs verrous sans compromettre l'intégrité des données. Si cela est possible ou non, il faut y réfléchir et ce n'est pas toujours le cas. La structure des données est également le facteur contributif à la décision. Donc, si nous utilisons un grand tableau pour implémenter une table de hachage, l'utilisation d'un seul verrou pour toute la table de hachage pour la synchronisation entraînera un accès séquentiel des threads à la structure de données. S'il s'agit du même emplacement sur la table de hachage, il est nécessaire, mais que se passe-t-il s'ils accèdent aux deux extrêmes de la table.

L'inconvénient de la répartition des verrous est qu'il est difficile d'obtenir l'état de la structure de données affectée par la répartition. Dans l'exemple, la taille de la table ou essayer de répertorier/énumérer la table entière peut être fastidieux car nous devons acquérir tous les verrous entrelacés.

2

Quelle réponse attendez-vous ici?

C'est va évidemment dépendre du nombre de lectures qui se produisent en même temps que les écritures et combien de temps une carte normale doit être "verrouillé" sur une opération d'écriture dans votre application (et si vous utiliseriez la méthode putIfAbsent sur ConcurrentMap). Tout indice de référence va être largement vide de sens.

0
oxbow_lakes

Ce n'est pas clair ce que tu veux dire. Si vous avez besoin de la sécurité des threads, vous n'avez presque pas le choix - seulement ConcurrentHashMap. Et il y a certainement des pénalités de performance/mémoire dans l'appel get () - accès aux variables volatiles et verrouillage si vous n'avez pas de chance.

0
Vitaly