Il y a longtemps, j'ai acheté un livre de structures de données hors de la table de négociation pour 1,25 $. Dans ce document, l'explication d'une fonction de hachage a déclaré qu'elle devrait finalement être modifiée par un nombre premier en raison de "la nature des mathématiques".
Qu'attendez-vous d'un livre de 1,25 $?
Quoi qu'il en soit, j'ai eu des années pour réfléchir à la nature des mathématiques et je n'arrive toujours pas à le comprendre.
La distribution des nombres est-elle vraiment plus même lorsqu'il y a un nombre premier de seaux? Ou est-ce un vieux conte de programmeur que tout le monde accepte parce que tout le monde l' accepte?
language-agnostic
data-structures
hash
theschmitzer
la source
la source
Réponses:
Habituellement, une fonction de hachage simple fonctionne en prenant les «composants» de l'entrée (caractères dans le cas d'une chaîne), en les multipliant par les puissances d'une constante et en les ajoutant ensemble dans un type entier. Ainsi, par exemple, un hachage typique (bien que pas particulièrement bon) d'une chaîne pourrait être:
Ensuite, si un tas de chaînes ayant toutes le même premier caractère sont introduites, alors les résultats seront tous les mêmes modulo k, au moins jusqu'à ce que le type entier déborde.
[Par exemple, la chaîne hashCode de Java est étrangement similaire à cela - elle fait l'ordre inverse des caractères, avec k = 31. Vous obtenez donc des relations de frappe modulo 31 entre des chaînes qui se terminent de la même manière, et des relations de frappe modulo 2 ^ 32 entre des chaînes qui sont les mêmes sauf près de la fin. Cela ne gâche pas sérieusement le comportement de la table de hachage.]
Une table de hachage fonctionne en prenant le module du hachage sur le nombre de seaux.
Il est important dans une table de hachage de ne pas produire de collisions pour les cas probables, car les collisions réduisent l'efficacité de la table de hachage.
Supposons maintenant que quelqu'un mette tout un tas de valeurs dans une table de hachage ayant une relation entre les éléments, comme tous ayant le même premier caractère. Il s'agit d'un modèle d'utilisation assez prévisible, je dirais, donc nous ne voulons pas qu'il produise trop de collisions.
Il s'avère que "en raison de la nature des mathématiques", si la constante utilisée dans le hachage et le nombre de compartiments sont coprimes , les collisions sont minimisées dans certains cas courants. S'ils ne sont pas coprime, il existe alors des relations assez simples entre les entrées pour lesquelles les collisions ne sont pas minimisées. Tous les hachages sont égaux modulo au facteur commun, ce qui signifie qu'ils tomberont tous dans le 1 / nème des seaux qui ont cette valeur modulo le facteur commun. Vous obtenez n fois plus de collisions, où n est le facteur commun. Puisque n est au moins 2, je dirais qu'il est inacceptable qu'un cas d'utilisation assez simple génère au moins deux fois plus de collisions que la normale. Si un utilisateur va diviser notre distribution en seaux, nous voulons que ce soit un accident bizarre, pas une simple utilisation prévisible.
Maintenant, les implémentations de table de hachage n'ont évidemment aucun contrôle sur les éléments qui y sont placés. Ils ne peuvent pas empêcher leur relation. Donc, la chose à faire est de s'assurer que le nombre de constantes et de seaux est coprime. De cette façon, vous ne comptez pas uniquement sur le "dernier" composant pour déterminer le module du godet par rapport à un petit facteur commun. Pour autant que je sache, ils n'ont pas besoin d'être les meilleurs pour y parvenir, juste du coprime.
Mais si la fonction de hachage et la table de hachage sont écrites indépendamment, la table de hachage ne sait pas comment fonctionne la fonction de hachage. Il peut s'agir d'une constante avec de petits facteurs. Si vous êtes chanceux, cela pourrait fonctionner complètement différemment et être non linéaire. Si le hachage est assez bon, alors tout nombre de seaux est très bien. Mais une table de hachage paranoïaque ne peut pas assumer une bonne fonction de hachage, elle doit donc utiliser un nombre premier de compartiments. De même, une fonction de hachage paranoïde devrait utiliser une constante première de grande taille, pour réduire le risque que quelqu'un utilise un certain nombre de compartiments, ce qui se trouve avoir un facteur commun avec la constante.
En pratique, je pense qu'il est assez normal d'utiliser une puissance de 2 comme nombre de godets. Ceci est pratique et évite d'avoir à chercher ou à présélectionner un nombre premier de la bonne ampleur. Vous comptez donc sur la fonction de hachage pour ne pas utiliser de multiplicateurs pairs, ce qui est généralement une hypothèse sûre. Mais vous pouvez toujours obtenir de mauvais comportements de hachage occasionnels basés sur des fonctions de hachage comme celle ci-dessus, et le nombre de compartiments principaux pourrait aider davantage.
Mettre sur le principe que "tout doit être premier" est autant que je sache une condition suffisante mais pas nécessaire pour une bonne distribution sur les tables de hachage. Il permet à chacun d'interagir sans avoir à supposer que les autres ont suivi la même règle.
[Modifier: il existe une autre raison, plus spécialisée, d'utiliser un nombre premier de compartiments, à savoir si vous gérez les collisions avec un sondage linéaire. Ensuite, vous calculez une foulée à partir du code de hachage, et si cette foulée s'avère être un facteur du nombre de compartiments, vous ne pouvez effectuer que des sondes (bucket_count / stride) avant de revenir où vous avez commencé. Le cas que vous voulez éviter le plus est stride = 0, bien sûr, qui doit être spécial, mais pour éviter également que bucket_count / stride soit égal à un petit entier, vous pouvez simplement faire le bucket_count premier et ne vous souciez pas de ce que le foulée est fournie, ce n'est pas 0.]
la source
La première chose que vous faites lorsque vous insérez / récupérez à partir de la table de hachage est de calculer le hashCode pour la clé donnée, puis de trouver le compartiment correct en ajustant le hashCode à la taille de la table de hachage en exécutant hashCode% table_length. Voici 2 «déclarations» que vous avez probablement lues quelque part
Et voici la preuve.
Si vous supposez que votre fonction hashCode donne les hashCodes suivants entre autres {x, 2x, 3x, 4x, 5x, 6x ...}, alors tous ces éléments vont être regroupés en un nombre m de compartiments, où m = table_length / GreatestCommonFactor (longueur_table, x). (Il est trivial de vérifier / dériver cela). Vous pouvez maintenant effectuer l'une des opérations suivantes pour éviter le clustering
Assurez-vous que vous ne générez pas trop de hashCodes qui sont des multiples d'un autre hashCode comme dans {x, 2x, 3x, 4x, 5x, 6x ...}. Mais cela peut être un peu difficile si votre table de hachage est censée avoir des millions d'entrées. Ou faites simplement m égal à la table_length en faisant GreatestCommonFactor (table_length, x) égal à 1, c'est-à-dire en faisant coprime table_length avec x. Et si x peut être à peu près n'importe quel nombre, assurez-vous que table_length est un nombre premier.
De - http://srinvis.blogspot.com/2006/07/hash-table-lengths-and-prime-numbers.html
la source
http://computinglife.wordpress.com/2008/11/20/why-do-hash-functions-use-prime-numbers/
Explication assez claire, avec des photos aussi.
Modifier: En résumé, les nombres premiers sont utilisés parce que vous avez la meilleure chance d'obtenir une valeur unique en multipliant les valeurs par le nombre premier choisi et en les additionnant tous. Par exemple, étant donné une chaîne, la multiplication de chaque valeur de lettre par le nombre premier, puis l'addition de tous, vous donnera sa valeur de hachage.
Une meilleure question serait, pourquoi exactement le nombre 31?
la source
*32
s'agit d'un simple décalage de bits, ou mieux encore d'un facteur d'échelle d'adresse immédiate (par exemplelea eax,eax*8; leax, eax,eax*4
sur x86 / x64). C'est donc*31
un bon candidat pour la multiplication des nombres premiers. C'était à peu près vrai il y a quelques années - maintenant la dernière architecture des processeurs a une multiplication presque instantanée - la division est toujours plus lente ...tl; dr
index[hash(input)%2]
entraînerait une collision pour la moitié de tous les hachages possibles et une plage de valeurs.index[hash(input)%prime]
entraîne une collision de <2 de tous les hachages possibles. La fixation du diviseur à la taille de la table garantit également que le nombre ne peut pas être supérieur à la table.la source
Les amorces sont utilisées parce que vous avez de bonnes chances d'obtenir une valeur unique pour une fonction de hachage typique qui utilise des polynômes modulo P. Dites, vous utilisez une telle fonction de hachage pour des chaînes de longueur <= N, et vous avez une collision. Cela signifie que 2 polynômes différents produisent la même valeur modulo P. La différence de ces polynômes est là encore un polynôme de même degré N (ou moins). Il n'a pas plus de N racines (c'est ici que la nature des mathématiques se montre, car cette affirmation n'est vraie que pour un polynôme sur un champ => nombre premier). Donc, si N est bien inférieur à P, il est probable que vous n'ayez pas de collision. Après cela, l'expérience peut probablement montrer que 37 est assez grand pour éviter les collisions pour une table de hachage de chaînes de longueur 5-10, et assez petit pour être utilisé pour les calculs.
la source
Juste pour fournir un autre point de vue, il y a ce site:
http://www.codexon.com/posts/hash-functions-the-modulo-prime-myth
Ce qui signifie que vous devez utiliser le plus grand nombre de compartiments possible au lieu d'arrondir à un nombre premier de compartiments. Cela semble être une possibilité raisonnable. Intuitivement, je peux certainement voir comment un plus grand nombre de seaux serait mieux, mais je ne peux pas en faire un argument mathématique.
la source
http://computinglife.wordpress.com/2008/11/20/why-do-hash-functions-use-prime-numbers/
la source
Cela dépend du choix de la fonction de hachage.
De nombreuses fonctions de hachage combinent les différents éléments des données en les multipliant avec certains facteurs modulo la puissance de deux correspondant à la taille des mots de la machine (ce module est libre en laissant simplement déborder le calcul).
Vous ne voulez pas de facteur commun entre un multiplicateur pour un élément de données et la taille de la table de hachage, car il pourrait alors arriver que la variation de l'élément de données ne répartisse pas les données sur l'ensemble de la table. Si vous choisissez un nombre premier pour la taille de la table, un tel facteur commun est hautement improbable.
D'un autre côté, ces facteurs sont généralement constitués de nombres premiers impairs, vous devez donc également être sûr d'utiliser des puissances de deux pour votre table de hachage (par exemple, Eclipse utilise 31 lorsqu'il génère la méthode Java hashCode ()).
la source
Supposons que la taille de votre table (ou le nombre pour modulo) soit T = (B * C). Maintenant, si le hachage de votre entrée est comme (N * A * B) où N peut être n'importe quel entier, alors votre sortie ne sera pas bien distribuée. Parce que chaque fois que n devient C, 2C, 3C etc., votre sortie commencera à se répéter. c'est-à-dire que votre sortie sera distribuée uniquement dans les positions C. Notez que C ici est (T / HCF (taille de table, hachage)).
Ce problème peut être éliminé en créant HCF 1. Les nombres premiers sont très bons pour cela.
Une autre chose intéressante est lorsque T est 2 ^ N. Ceux-ci donneront une sortie exactement identique à tous les N bits inférieurs du hachage d'entrée. Comme chaque nombre peut être représenté par des puissances de 2, lorsque nous prendrons le modulo de n'importe quel nombre avec T, nous soustraireons toutes les puissances de 2 sous forme de nombre, qui sont> = N, ce qui donne toujours le nombre de motifs spécifiques, en fonction de l'entrée . C'est aussi un mauvais choix.
De même, T comme 10 ^ N est également mauvais pour des raisons similaires (modèle en notation décimale des nombres au lieu de binaire).
Ainsi, les nombres premiers ont tendance à donner des résultats mieux distribués, donc sont un bon choix pour la taille du tableau.
la source
Je pense que cela vient du fait que les ordinateurs fonctionnent avec la base 2. Pensez simplement à la façon dont la même chose fonctionne pour la base 10:
Peu importe le nombre: tant qu'il se termine par 8, son modulo 10 sera 8.
Choisir un nombre suffisamment grand, sans puissance de deux, garantira que la fonction de hachage est vraiment une fonction de tous les bits d'entrée, plutôt qu'un sous-ensemble d'entre eux.
la source
Je voudrais ajouter quelque chose pour la réponse de Steve Jessop (je ne peux pas en parler car je n'ai pas assez de réputation). Mais j'ai trouvé du matériel utile. Sa réponse est très utile, mais il a fait une erreur: la taille du seau ne devrait pas être une puissance de 2. Je citerai simplement le livre "Introduction to Algorithm" de Thomas Cormen, Charles Leisersen, et al à la page 263:
J'espère que ça aide.
la source
Pour une fonction de hachage, il est non seulement important de minimiser les collisions en général, mais de rendre impossible le maintien du même hachage tout en changeant quelques octets.
Disons que vous avez une équation:
(x + y*z) % key = x
avec0<x<key
et0<z<key
. Si la clé est un nombre primitif n * y = la clé est vraie pour tous les n dans N et fausse pour tous les autres nombres.Un exemple où clé n'est pas un excellent exemple: x = 1, z = 2 et clé = 8 Parce que clé / z = 4 est toujours un nombre naturel, 4 devient une solution pour notre équation et dans ce cas (n / 2) * La clé y = est vraie pour chaque n dans N. Le nombre de solutions pour l'équation a pratiquement doublé car 8 n'est pas un nombre premier.
Si notre attaquant sait déjà que 8 est une solution possible pour l'équation, il peut changer le fichier de produire 8 à 4 et obtient toujours le même hachage.
la source
J'ai lu le site Web wordpress populaire lié dans certaines des réponses populaires ci-dessus en haut. D'après ce que j'ai compris, je voudrais partager une simple observation que j'ai faite.
Vous pouvez trouver tous les détails dans l'article ici , mais supposez que ce qui suit est vrai:
Une implémentation de hashmap générale veut que 2 choses soient uniques.
Comment obtenir l'index unique? En faisant également de la taille initiale du conteneur interne une prime. Donc, fondamentalement, Prime est impliqué car il possède cette caractéristique unique de produire des nombres uniques que nous finissons par utiliser pour identifier des objets et trouver des index à l'intérieur du conteneur interne.
Exemple:
clé = "clé"
valeur = "valeur"
uniqueId = "k" * 31 ^ 2 + "e" * 31 ^ 1` + "y"
correspond à un identifiant unique
Maintenant, nous voulons un emplacement unique pour notre valeur - nous
uniqueId % internalContainerSize == uniqueLocationForValue
, en supposant queinternalContainerSize
c'est aussi une prime.Je sais que c'est simplifié, mais j'espère avoir une idée générale.
la source
"La nature des mathématiques" concernant les modules de puissance principale est qu'ils sont un élément constitutif d'un champ fini . Les deux autres éléments constitutifs sont une opération d'addition et de multiplication. La propriété spéciale des modules premiers est qu'ils forment un champ fini avec les opérations d'addition et de multiplication "régulières", qui viennent d'être amenées au module. Cela signifie que chaque multiplication correspond à un module entier différent du nombre premier, tout comme chaque ajout.
Les modules Prime sont avantageux car:
Cependant, ils ont un gros inconvénient, ils nécessitent une division entière, ce qui prend de nombreux cycles (~ 15-40), même sur un processeur moderne. Avec environ la moitié du calcul, on peut s'assurer que le hachage est très bien mélangé. Deux multiplications et xorshift se mélangeront mieux qu'un moudulus premier. Ensuite, nous pouvons utiliser la taille de table de hachage et la réduction de hachage la plus rapide, ce qui donne 7 opérations au total pour une puissance de 2 tailles de table et environ 9 opérations pour des tailles arbitraires.
J'ai récemment examiné la plupart des implémentations de table de hachage les plus rapides et la plupart d'entre elles n'utilisent pas de modules principaux.
la source
Cette question a été fusionnée avec la question la plus appropriée, pourquoi les tables de hachage devraient utiliser des tableaux de taille optimale, et non une puissance de 2. Pour les fonctions de hachage elles-mêmes, il y a beaucoup de bonnes réponses ici, mais pour la question connexe, pourquoi certaines tables de hachage critiques pour la sécurité , comme la glibc, utilisez des tableaux de grande taille, il n'y en a pas encore.
Généralement, la puissance de 2 tables est beaucoup plus rapide. Il y en a cher
h % n => h & bitmask
, où le bitmask peut être calculé viaclz
("count leader zeros") de la taille n. Une fonction modulo doit effectuer une division entière qui est environ 50 fois plus lente qu'une logiqueand
. Il y a quelques astuces pour éviter un modulo, comme utiliser https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ de Lemire , mais les tables de hachage rapides utilisent généralement la puissance de 2, et les tables de hachage sécurisées utilisent des nombres premiers.Pourquoi
Dans ce cas, la sécurité est définie par des attaques contre la stratégie de résolution des collisions, qui, avec la plupart des tables de hachage, n'est qu'une recherche linéaire dans une liste chaînée de collisions. Ou avec la recherche linéaire des tables d'adressage ouvert plus rapide directement dans la table. Ainsi, avec la puissance de 2 tables et certaines connaissances internes de la table, par exemple la taille ou l'ordre de la liste de clés fournie par une interface JSON, vous obtenez le nombre de bons bits utilisés. Le nombre de ceux du bitmask. Ceci est généralement inférieur à 10 bits. Et pour 5 à 10 bits, il est trivial de subir des collisions par force brute, même avec les fonctions de hachage les plus puissantes et les plus lentes. Vous n'obtenez plus la sécurité complète de vos fonctions de hachage 32 bits ou 64 bits. Et le but est d'utiliser de petites fonctions de hachage rapides, pas des monstres comme le murmure ou même le siphash.
Donc, si vous fournissez une interface externe à votre table de hachage, comme un résolveur DNS, un langage de programmation, ... vous voulez vous soucier des utilisateurs abusifs qui aiment DOS de tels services. Il est normalement plus facile pour ces gens de fermer votre fonction publique avec des méthodes beaucoup plus faciles, mais c'est arrivé. Les gens s'en sont donc souciés.
Ainsi, les meilleures options pour éviter de telles attaques par collision sont soit
1) d'utiliser des tables principales, car alors
2) utilisez de meilleures mesures contre l'attaque réelle, ainsi qu'une puissance rapide de 2 tailles.
Il existe un mythe largement répandu selon lequel des fonctions de hachage plus sécurisées aident à prévenir de telles attaques, ce qui est faux comme je l'ai expliqué. Il n'y a pas de sécurité uniquement avec des bits faibles. Cela ne fonctionnerait qu'avec des tables de taille optimale, mais cela utiliserait une combinaison des deux méthodes les plus lentes, le hachage lent et le modulo premier lent.
Les fonctions de hachage pour les tables de hachage doivent principalement être petites (pour être intégrées) et rapides. La sécurité ne peut venir que d'empêcher la recherche linéaire dans les collisions. Et ne pas utiliser des fonctions de hachage trivialement mauvaises, comme celles insensibles à certaines valeurs (comme \ 0 lors de l'utilisation de la multiplication).
L'utilisation de graines aléatoires est également une bonne option, les gens ont commencé par cela en premier, mais avec suffisamment d'informations sur la table, même une graine aléatoire n'aide pas beaucoup, et les langages dynamiques rendent généralement trivial d'obtenir la graine via d'autres méthodes, car elle est stockée dans emplacements de mémoire connus.
la source
la source