77 votes

Pourquoi n ' t String ' cache de hashCode() s 0 ?

J'ai remarqué que dans la version 6 de Java code source pour la Chaîne qui hashCode seulement les caches des valeurs autres que 0. La différence de performance est présenté par le fragment de code suivant:

public class Main{
   static void test(String s) {
      long start = System.currentTimeMillis();
      for (int i = 0; i < 10000000; i++) {
         s.hashCode();
      }
      System.out.format("Took %d ms.%n", System.currentTimeMillis() - start);
   }
   public static void main(String[] args) {
      String z = "Allocator redistricts; strict allocator redistricts strictly.";
      test(z);
      test(z.toUpperCase());
   }
}

L'exécution de cette dans ideone.com donne le résultat suivant:

Took 1470 ms.
Took 58 ms.

Donc mes questions sont:

  • Pourquoi ne pas la Chaîne de hashCode() cache de 0?
  • Quelle est la probabilité qu'un Java chaîne de hachages à 0?
  • Quelle est la meilleure façon d'éviter la dégradation des performances de recalculer la valeur de hachage de tous les temps pour les chaînes de hachage à 0?
  • Est-ce le meilleur-pratique de la méthode de mise en cache des valeurs? (c'est à dire cache tout sauf un?)

Pour votre amusement, chaque ligne ici est une chaîne de hachage à 0:

pollinating sandboxes
amusement & hemophilias
schoolworks = perversive
electrolysissweeteners.net
constitutionalunstableness.net
grinnerslaphappier.org
BLEACHINGFEMININELY.NET
WWW.BUMRACEGOERS.ORG
WWW.RACCOONPRUDENTIALS.NET
Microcomputers: the unredeemed lollipop...
Incentively, my dear, I don't tessellate a derangement.
A person who never yodelled an apology, never preened vocalizing transsexuals.

59voto

Kevin Bourrillion Points 19677

Vous êtes vous soucier de rien. Voici une façon de réfléchir à cette question.

Supposons que vous disposez d'une application qui ne fait rien, mais de s'asseoir autour de hachage Cordes tout au long de l'année. Disons qu'il prend un millier de chaînes, tout en mémoire, les appels hashCode() sur eux à plusieurs reprises dans le round robin, un million de fois à travers, puis obtient un autre millier de chaînes de caractères et le fait encore.

Et supposons que la probabilité d'une chaîne de code de hachage de l'être zéro ont été, en fait, beaucoup plus grande que 1/2^32. Je suis sûr que c'est un peu plus de 1/2^32, mais disons que c'est bien pire que ça, 1/2^16 (la racine carrée! maintenant c'est bien pire!).

Dans cette situation, vous avez plus de bénéficier de l'Oracle ingénieurs de l'amélioration de la façon dont ces cordes des codes de hachage sont mis en cache que quiconque vivant. Si vous écrivez pour eux et leur demander de le réparer. Et ils travaillent leur magie de sorte que, à chaque fois.hashCode() est égale à zéro, il retourne instantanément (même la première fois! une amélioration de 100%!). Et disons qu'ils le font sans en dégrader la performance à tous les pour tout autre cas.

Hourra! Maintenant, votre application est... voyons voir... à 0,0015% plus rapide!

Ce qui sert à prendre une journée entière maintenant, ne prend que 23 heures, 57 minutes et 48 secondes!

Et rappelez-vous, nous avons mis en place le scénario de donner à tous le bénéfice du doute, souvent à un ridicule degré.

Cela vous semble vaut le coup pour vous?

EDIT: depuis l'affichage de ce il ya quelques heures, j'ai laissé un de mes processeurs à l'état sauvage à la recherche de deux mots des phrases avec zéro des codes de hachage. Jusqu'à présent, il est venu avec: bequirtle zorillo, chronogrammic schtoff, contondante cloisterlike, creashaks organzine, drumwood boulderhead, electroanalytic exerçables, et favosely nonconstruable. C'est de l'ordre de 2^35 possibilités, donc avec une parfaite distribution nous nous attendons à voir seulement 8. Clairement par le temps, il est fait, nous allons avoir un peu de temps, beaucoup, mais pas bourré de plus. Ce qui est plus significatif, c'est que j'ai maintenant venir avec quelques intéressantes bande de noms/noms d'album! C'est pas juste voler!

24voto

Jon Skeet Points 692016

Il utilise 0 pour indiquer "je n'ai pas travaillé sur le hashcode encore". L'alternative serait d'utiliser un indicateur Booléen, ce qui pourrait prendre plus de mémoire. (Ou de ne pas mettre en cache le hashcode à tous, bien sûr.)

Je n'attends pas beaucoup de chaînes de hachage à 0; sans doute il serait judicieux pour le hachage de routine afin d'éviter délibérément 0 (par exemple, traduire une table de hachage de 0 à 1, et cache). Ce qui permettrait d'augmenter les collisions, mais éviter de ressasser. Il est trop tard pour le faire maintenant, même si, comme la Chaîne de hashCode algorithme est explicitement décrite.

Quant à savoir si c'est une bonne idée en général: c'est un certainement efficace mécanisme de mise en cache, et pourrait (voir edit) être encore mieux avec un changement pour éviter de ressasser les valeurs qui finissent avec un hachage de 0. Personnellement, je serais intéressé de voir les données qui conduit Soleil à croire que c'était la peine de le faire, en premier lieu - c'est de prendre un supplément de 4 octets pour chaque chaîne de jamais créé, mais souvent, ou très rarement, c'est haché, et le seul avantage est pour les chaînes qui sont hachés plus d'une fois.

EDIT: Comme KevinB souligne dans un commentaire ailleurs, le "éviter de 0" suggestion ci-dessus peut-être bien une nette coût car il participe à de très rares cas, mais qui nécessite une comparaison supplémentaire pour chaque calcul de hachage.

19voto

MB. Points 2847

Je pense qu'il y a quelque chose d'important que les autres réponses sont manquantes: la valeur zéro, de façon à ce que le hashCode-mécanisme de mise en cache fonctionne de manière robuste dans un environnement multi-thread.

Si vous aviez deux variables, comme cachedHashCode lui-même et un isHashCodeCalculated booléen pour indiquer si cachedHashCode avait été calculé, vous avez besoin de synchronisation de thread pour que les choses marchent dans un environnement multithread. Et la synchronisation serait mauvais pour la performance, surtout depuis que les Chaînes sont très souvent réutilisés dans plusieurs threads.

Ma compréhension de la mémoire Java modèle est un peu sommaire, mais voici à peu près ce qu'il se passe:

  1. Lorsque plusieurs threads accèdent à une variable (comme la mise en cache hashCode), il n'y a aucune garantie que chaque thread va voir la dernière valeur. Si une variable commence à zéro, puis Un jour (il définit à une valeur non nulle), alors le fil B lit peu de temps après, le fil B pouvait encore voir la valeur zéro.

  2. Il y a un autre problème avec l'accès à des valeurs partagées à partir de plusieurs threads (sans synchronisation) - vous pouvez essayer d'utiliser un objet qui n'a été en partie initialisé (construction d'un objet n'est pas atomique). Multi-thread lit et écrit de 64 bits primitives comme les longs et les doubles ne sont pas nécessairement atomique, donc si deux threads tentent de lire et de modifier la valeur d'un long ou d'un lit double, un thread peut voir quelque chose de bizarre et partiellement défini. Ou quelque chose comme ça de toute façon. Il y a des problèmes semblables, si vous essayez d'utiliser deux variables entre elles, comme cachedHashCode et isHashCodeCalculated - un thread peut facilement venir et voir la dernière version de l'un de ces variables, mais une version plus ancienne de l'autre.

  3. La façon habituelle de contourner ces multi-threading problèmes est d'utiliser la synchronisation. Par exemple, vous pourriez mettre tous les accès à la version en cache de hashCode à l'intérieur d'un bloc synchronisé, ou vous pouvez utiliser le mot clé volatile (mais être prudent avec ce que parce que les règles sont un peu confus).

  4. Toutefois, la synchronisation ralentit les choses. Mauvaise idée pour quelque chose comme une chaîne de hashCode. Les chaînes sont très souvent utilisés comme clés dans HashMaps, si vous avez besoin de la méthode hashCode à bien, y compris dans les environnements multi-threadés.

  5. Java primitives qui sont en 32 bits ou moins, comme int, sont spéciales. Contrairement, disons, une longue (valeur 64 bits), vous pouvez être sûr que vous ne serez jamais lu une partie initialisé la valeur d'un int (32 bits). Lorsque vous lisez un int sans synchronisation, vous ne pouvez pas être sûr que vous obtiendrez la dernière valeur réglée, mais vous pouvez être sûr que la valeur que vous obtenez est une valeur qui a été explicitement définie à un certain point, par votre fil ou un autre thread.

Le hashCode mécanisme de mise en cache en java.lang.La chaîne est mis en place pour appuyer sur le point 5 ci-dessus. Vous comprendrez mieux en regardant la source de java.lang.Chaîne de caractères.hashCode(). En fait, avec plusieurs threads d'appel hashCode à la fois, hashCode pourrait être calculé plusieurs fois (si la valeur calculée est égale à zéro ou si plusieurs threads appel hashCode à la fois et de voir à zéro la valeur mise en cache), mais vous pouvez être sûr que hashCode() renverra toujours la même valeur. Il est donc robuste, et il est trop performant (car il n'y a pas de synchronisation à agir comme un goulot d'étranglement dans les environnements multi-threads).

Comme je l'ai dit, ma compréhension de la mémoire Java modèle est un peu sommaire, mais je suis sûr que j'ai de l'essence de la ci-dessus à droite. Finalement, c'est un très habile idiome pour la mise en cache du hashCode sans les frais de synchronisation.

8voto

Adamski Points 29884

0 n'est pas mis en cache dans la mise en œuvre interprète une valeur mise en cache de 0 "valeur mise en cache pas encore initialisée". L'alternative aurait été d'utiliser un java.lang.Integer, selon lequel nulle implique que la valeur n'était pas encore mis en cache. Cependant, cela aurait signifié un supplément de surcharge de stockage.

Au sujet de la probabilité d'une Chaîne de code de hachage de l'être calculé comme 0 je dirais que la probabilité est très faible et peut se produire dans les cas suivants:

  • La Chaîne est vide (bien que recalculant ce code de hachage de chaque temps est effectivement O(1)).
  • Un dépassement de capacité se produit en vertu de laquelle la finale calculée le code de hachage est de 0 (e.g. Integer.MAX_VALUE + h(c1) + h(c2) + ... h(cn) == 0).
  • La Chaîne ne contient que des caractères Unicode 0. Très rare car c'est un caractère de contrôle avec aucune signification en dehors de la "bande de papier du monde" (!):

De Wikipedia:

Le Code 0 (ASCII nom de code NUL) est un cas spécial. Dans la bande de papier, c'est la cas quand il n'y a pas de trous. Il est commode de traiter cela comme un remplissage personnage sans sens contraire.

6voto

cdunn2001 Points 3597

Cela s’avère être une bonne question, concernant une vulnérabilité de sécurité.

« Lors du hachage d’une chaîne, Java met également en cache la valeur de hachage dans l’attribut hash, mais seulement si le résultat est différent de zéro. Ainsi, la valeur cible zéro est particulièrement intéressante pour un attaquant car il empêche la mise en cache et re-hachage des forces. »

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X