Pourquoi les fonctions de hachage devraient-elles utiliser un module de nombre premier?

336

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?

theschmitzer
la source
1
Question parfaitement raisonnable: pourquoi devrait-il y avoir un nombre premier de seaux?
Draemon
1
Cette question semble être hors sujet car elle appartient très probablement à l' informatique .
Courses de légèreté en orbite du
2
cs.stackexchange.com/a/64191/64222 une autre explication bien argumentée.
Green Tree
Voici une autre excellente explication à une question quelque peu connexe avec des chiffres probants surprenants - quora.com/…
AnBisw

Réponses:

242

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:

(first char) + k * (second char) + k^2 * (third char) + ...

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.]

Steve Jessop
la source
Juste comme note latérale: une discussion pour un choix judicieux du facteur k pour hashCodes est ici: stackoverflow.com/q/1835976/21499
Hans-Peter Störr
9
c'est une réponse impressionnante. pouvez-vous expliquer cela plus en détail? "Ainsi, vous obtenez des relations modulo 31 entre des chaînes qui se terminent de la même manière, et des relations modulo 2 ^ 32 entre des chaînes qui sont identiques, sauf vers la fin. Cela ne gâche pas sérieusement le comportement de la table de hachage. " Je ne comprends surtout pas la partie 2 ^ 32
ordinaire
2
Note supplémentaire pour clarifier les choses à ce sujet: "Tous les hachages sont égaux modulo le facteur commun" -> En effet, si vous considérez l'exemple de la fonction de hachage hash = 1er char + 2e char * k + ..., et prendre des chaînes avec le même premier caractère, le hachage% k sera le même pour ces chaînes. Si M est la taille de la table de hachage et g est le pgcd de M et k, alors (hachage% k)% g est égal au hachage% g (puisque g divise k) et donc le hachage% g sera également le même pour ces chaînes. Considérons maintenant (hachage% M)% g, ce qui est égal au hachage% g (puisque g divise M). Donc (hachage% M)% g est égal pour toutes ces chaînes.
Quark
1
@DanielMcLaury Joshua Bloch a expliqué pourquoi pour Java - il était recommandé dans deux livres populaires (K&R, Dragon book) et fonctionnait bien avec de faibles collisions dans le dictionnaire anglais. C'est rapide (utilise la méthode de Horner ). Apparemment, même K&R ne se souvient pas d'où cela vient. La fonction similaire est l' empreinte digitale de Rabin issue de l' algorithme de Rabin-Karp (1981), mais K&R (1978) la précède.
bain
1
@SteveJessop, pouvez-vous expliquer "les relations frappantes modulo 2 ^ 32 entre des chaînes qui sont les mêmes sauf vers la fin."? Merci.
Khanna111
29

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

  1. Si vous utilisez une puissance de 2 pour table_length, la recherche de (hashCode (key)% 2 ^ n) est aussi simple et rapide que (hashCode (key) & (2 ^ n -1)). Mais si votre fonction de calcul du hashCode pour une clé donnée n'est pas bonne, vous souffrirez certainement du regroupement de nombreuses clés dans quelques compartiments de hachage.
  2. Mais si vous utilisez des nombres premiers pour table_length, les hashCodes calculés pourraient correspondre aux différents compartiments de hachage même si vous avez une fonction de hashCode légèrement stupide.

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
11

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?

AlbertoPL
la source
5
Bien que je pense qu'un résumé serait utile, au cas où ce site serait mort, certains restes de son contenu seront enregistrés ici sur SO.
Thomas Owens
2
L'article n'explique pas pourquoi, mais dit: "Les chercheurs ont constaté que l'utilisation d'un nombre premier de 31 donne une meilleure distribution des touches, et moins de collisions. Personne ne sait pourquoi ..." Drôle, posant la même question que moi en effet .
theschmitzer
> Une meilleure question serait, pourquoi exactement le nombre 31? Si vous voulez dire pourquoi le nombre 31 est utilisé, l'article que vous pointez vous explique pourquoi, c'est-à-dire parce qu'il est rapide à multiplier et que les tests cos montrent qu'il est le meilleur à utiliser. L'autre multiplicateur populaire que j'ai vu est 33, ce qui donne du poids à la théorie selon laquelle le problème de vitesse était (au moins initialement) un facteur important. Si vous voulez dire, qu'en est-il de 31 qui améliorent les tests, alors je crains de ne pas savoir.
sgmoore
Exactement, la seule raison pour laquelle il aurait pu être utilisé comme multiplicateur était qu'il était facile de le multiplier. (Quand je dis que j'ai vu 33 utilisé comme multiplicateur, je ne veux pas dire récemment, c'était probablement il y a des décennies, et c'était possible avant que beaucoup d'analyse ne soit faite sur le hachage).
sgmoore
3
@SteveJessop Le nombre 31 est facilement optimisé par le CPU comme une opération (x * 32) -1, dans laquelle il *32s'agit d'un simple décalage de bits, ou mieux encore d'un facteur d'échelle d'adresse immédiate (par exemple lea eax,eax*8; leax, eax,eax*4sur x86 / x64). C'est donc *31un 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 ...
Arnaud Bouchez
10

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.

Indoloration
la source
1
2 est un mec de nombre premier
Ganesh Chowdhary Sadanala
8

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.

TT_
la source
1
Bien que l'explication semble maintenant évidente, elle m'est arrivée après avoir lu un livre de A.Shen "Programming: Theorems and problems" (en russe), voir la discussion sur l'algorithme Rabin. Je ne sais pas s'il existe une traduction en anglais.
TT_
5

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.

Falaina
la source
Un plus grand nombre de godets signifie moins de collisions: voir le principe du pigeonhole.
Inconnu
11
@Unknown: Je ne pense pas que ce soit vrai. Veuillez me corriger si je me trompe, mais je crois que l'application du principe du pigeonhole aux tables de hachage ne vous permet d'affirmer qu'il y aura des collisions si vous avez plus d'éléments que de bacs, de ne pas tirer de conclusions sur la quantité ou la densité des collisions. Cependant, je pense toujours que le plus grand nombre de bacs est le bon chemin.
Falaina
Si vous supposez que les collisions sont aléatoires à toutes fins utiles, alors, par le paradoxe d'anniversaire, un espace plus grand (seaux) réduira la probabilité d'une collision.
Inconnu
1
@Unknown vous avez manqué que les collisions dépendent également de la fonction de hachage elle-même. Donc, si la fonction has est vraiment mauvaise, peu importe la taille que vous augmentez, il peut toujours y avoir un nombre important de collisions
Suraj Chandran
L'article d'origine semble avoir disparu, mais il y a quelques commentaires perspicaces ici, y compris une discussion avec l'auteur original. news.ycombinator.com/item?id=650487
Adrian McCarthy
3

Les amorces sont des nombres uniques. Ils sont uniques en ce sens que le produit d'un nombre premier avec un autre numéro a les meilleures chances d'être unique (pas aussi unique que le nombre premier lui-même bien sûr) en raison du fait qu'un nombre premier est utilisé pour le composer. Cette propriété est utilisée dans les fonctions de hachage.

Étant donné une chaîne «Samuel», vous pouvez générer un hachage unique en multipliant chacun des chiffres ou lettres constitutifs par un nombre premier et en les additionnant. C'est pourquoi les nombres premiers sont utilisés.

Cependant, l'utilisation de nombres premiers est une ancienne technique. La clé ici pour comprendre que tant que vous pouvez générer une clé suffisamment unique, vous pouvez également passer à d'autres techniques de hachage. Allez ici pour en savoir plus sur ce sujet à propos de http://www.azillionmonkeys.com/qed/hash.html

http://computinglife.wordpress.com/2008/11/20/why-do-hash-functions-use-prime-numbers/

user105033
la source
1
hahahah .... en fait, le produit de 2 nombres premiers n'a-t-il pas de meilleures chances d'être «unique» que le produit d'un nombre premier et de tout autre nombre?
HasaniH
@Beska Ici, "l'unicité" est définie de manière récursive, donc je pense que la "non-unicité" devrait être définie de la même manière :)
TT_
3

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 ()).

starblue
la source
2

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.

nishantbhardwaj2002
la source
2

Copie de mon autre réponse https://stackoverflow.com/a/43126969/917428 . Voir pour plus de détails et d'exemples.

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:

  • 8% 10 = 8
  • 18% 10 = 8
  • 87865378% 10 = 8

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.

Ste_95
la source
1

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:

Lors de l'utilisation de la méthode de division, nous évitons généralement certaines valeurs de m. Par exemple, m ne devrait pas être une puissance de 2, car si m = 2 ^ p, alors h (k) n'est que les p bits de poids faible de k. À moins que nous ne sachions que tous les modèles de bits p d'ordre inférieur sont également probables, nous ferions mieux de concevoir la fonction de hachage pour qu'elle dépende de tous les bits de la clé. Comme l'exercice 11.3-3 vous le demande, choisir m = 2 ^ p-1 lorsque k est une chaîne de caractères interprétée dans le radix 2 ^ p peut être un mauvais choix, car permuter les caractères de k ne change pas sa valeur de hachage.

J'espère que ça aide.

iefgnoix
la source
0

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 = xavec 0<x<keyet0<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.

Christian
la source
0

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:

  • L'utilisation d'un nombre premier nous donne la "meilleure chance" d'une valeur unique

Une implémentation de hashmap générale veut que 2 choses soient uniques.

  • Code de hachage unique pour la clé
  • Index unique pour stocker la valeur réelle

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 que internalContainerSizec'est aussi une prime.

Je sais que c'est simplifié, mais j'espère avoir une idée générale.

Ryan
la source
0

"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:

  • Ils donnent le plus de liberté lors du choix du multiplicateur secondaire dans le hachage secondaire, tous les multiplicateurs sauf 0 finiront par visiter tous les éléments exactement une fois
  • Si tous les hachages sont inférieurs au module, il n'y aura pas de collision du tout
  • Les nombres premiers aléatoires se mélangent mieux que la puissance de deux modules et compressent les informations de tous les bits et pas seulement d'un sous-ensemble

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.

Wolfgang Brehm
la source
0

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é via clz("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 logique and. 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

  • tous les 32 ou 64 bits sont pertinents pour trouver le compartiment, pas seulement quelques-uns.
  • la fonction de redimensionnement de la table de hachage est plus naturelle que le simple double. La meilleure fonction de croissance est la séquence de fibonacci et les nombres premiers se rapprochent de cela plutôt que de doubler.

2) utilisez de meilleures mesures contre l'attaque réelle, ainsi qu'une puissance rapide de 2 tailles.

  • compter les collisions et abandonner ou dormir sur les attaques détectées, ce qui est un nombre de collisions avec une probabilité <1%. Comme 100 avec des tables de hachage 32 bits. C'est ce que fait par exemple le résolveur DNS de djb.
  • convertir la liste liée des collisions en arborescence avec la recherche O (log n) et non O (n) lorsqu'une attaque par collision est détectée. C'est ce que fait par exemple Java.

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.

rurban
la source
-1
function eratosthenes(n) {

    function getPrime(x) {
        var middle = (x-(x%2))/2;
        var arr_rest = [];
        for(var j=2 ; j<=middle;j++){
            arr_rest.push(x%j);
        }

        if(arr_rest.indexOf(0) == -1) {
            return true
        }else {
            return false
        }

    }
    if(n<2)  {
        return []
    }else if(n==2){
        return [2]
    }else {
        var arr = [2]
        for(var i=3;i<n;i++) {
            if(getPrime(i)){
                arr.push(i)
            }
        }
    }

    return arr;
}
Khaireddine Hamdi
la source
2
Pourriez-vous ajouter des commentaires pour expliquer votre solution, s'il vous plaît?
pom421