web-dev-qa-db-fra.com

Meilleur algorithme de hachage en termes de collisions de hachage et de performances pour les chaînes

Quel serait le meilleur algorithme de hachage si nous avions les priorités suivantes (dans cet ordre):

  1. Collisions de hachage minimales
  2. Performance

Il n'a pas besoin d'être sécurisé. Fondamentalement, j'essaie de créer un index basé sur une combinaison de propriétés de certains objets. Toutes les propriétés sont des chaînes.

Toute référence aux implémentations c # serait appréciée.

50
dpan

Oubliez le terme "meilleur". Quel que soit l'algorithme de hachage que l'on puisse trouver, à moins que vous n'ayez un ensemble très limité de données à hacher, chaque algorithme qui fonctionne très bien en moyenne peut devenir complètement inutile s'il n'est alimenté qu'avec les bonnes (ou selon votre point de vue) "fausses") données.

Au lieu de perdre trop de temps à réfléchir à la façon de rendre le hachage plus exempt de collision sans utiliser trop de temps CPU, je préfère commencer à penser à "Comment rendre les collisions moins problématiques". Par exemple. si chaque compartiment de hachage est en fait une table et que toutes les chaînes de cette table (qui ont eu une collision) sont triées alphabétiquement, vous pouvez rechercher dans une table de bucket en utilisant la recherche binaire (qui est uniquement O (log n)) et cela signifie, même lorsque chaque deuxième compartiment de hachage a 4 collisions, votre code aura toujours des performances décentes (il sera un peu plus lent par rapport à une table sans collision, mais pas tant que ça). Un gros avantage ici est que si votre table est assez grande et que votre hachage n'est pas trop simple, deux chaînes ayant pour résultat la même valeur de hachage seront généralement complètement différentes (par conséquent, la recherche binaire peut arrêter de comparer les chaînes après peut-être un ou deux caractères en moyenne ; rendre chaque comparaison très rapide).

En fait, j'ai moi-même eu une situation où la recherche directe dans une table triée à l'aide de la recherche binaire s'est avérée plus rapide que le hachage! Même si mon algorithme de hachage était simple, il a fallu un certain temps pour hacher les valeurs. Les tests de performances ont montré que seulement si j'obtiens plus de 700 à 800 entrées, le hachage est en effet plus rapide que la recherche binaire. Cependant, comme le tableau ne pouvait jamais dépasser plus de 256 entrées et que le tableau moyen était inférieur à 10 entrées, l'analyse comparative a clairement montré que sur chaque système, chaque processeur, la recherche binaire était plus rapide. Ici, le fait qu'habituellement déjà comparer le premier octet des données était suffisant pour conduire à la prochaine itération bsearch (car les données étaient très différentes dans le premier à deux octets déjà) s'est avéré être un gros avantage.

Donc, pour résumer: je prendrais un algorithme de hachage décent, qui ne cause pas trop de collisions en moyenne et est plutôt rapide (j'accepterais même quelques collisions de plus, si c'est juste très rapide!) Et plutôt optimiser mon code comment pour obtenir la plus petite pénalité de performances une fois que des collisions se produisent (et elles le feront! À moins que votre espace de hachage soit au moins égal ou supérieur à votre espace de données et que vous puissiez mapper une valeur de hachage unique à chaque ensemble de données possible).

33
Mecki

Comme Nigel Campbell indiqué, la `` meilleure '' fonction de hachage n'existe pas, car elle dépend des caractéristiques des données de ce que vous hachez ainsi que de la nécessité ou non de hacher de qualité cryptographique.

Cela dit, voici quelques conseils:

  • Étant donné que les éléments que vous utilisez comme entrée pour le hachage ne sont qu'un ensemble de chaînes, vous pouvez simplement combiner les codes de hachage pour chacune de ces chaînes individuelles. J'ai vu le pseudo-code suivant suggéré pour le faire, mais je ne connais aucune analyse particulière de celui-ci:

    int hashCode = 0;
    
    foreach (string s in propertiesToHash) {
        hashCode = 31*hashCode + s.GetHashCode();
    }
    

    Selon cet article , System.Web a une méthode interne qui combine les codes de hachage en utilisant

    combinedHash = ((combinedHash << 5) + combinedHash) ^ nextObj.GetHashCode();
    

    J'ai également vu du code qui simplement xor les codes de hachage ensemble, mais cela semble être une mauvaise idée pour moi (bien que je n'ai encore aucune analyse pour le sauvegarder). Si rien d'autre, vous vous retrouvez avec une collision si les mêmes chaînes sont hachées dans un ordre différent.

  • J'ai utilisé FNV à bon escient: http://www.isthe.com/chongo/tech/comp/fnv/

  • Paul Hsieh a un article décent: http://www.azillionmonkeys.com/qed/hash.html

  • Un autre article de Nice par Bob Jenkins qui a été initialement publié en 1997 dans le Journal du docteur Dobb (l'article lié a des mises à jour): http://burtleburtle.net/bob/hash/doobs.html

17
Michael Burr

Je vais être boiteux ici et donner une réponse plus théorique plutôt qu'une réponse précise, mais veuillez en prendre la valeur.

Il y a d'abord deux problèmes distincts:

une. Probabilité de collision b. Performances de hachage (c'est-à-dire: temps, cycles CPU, etc.)

Les deux problèmes sont légèrement corellés. Ils ne sont pas parfaitement corrélés.

Le problème a traite de la différence entre le hashee et les espaces de hachage résultants. Lorsque vous hachez un fichier de 1 Ko (1024 octets) et que le hachage contient 32 octets, il y aura:

1.0907481356194159294629842447338e + 2466 (c'est-à-dire un nombre avec 2466 zéros) combinaisons possibles de fichiers d'entrée

et l'espace de hachage aura

1,1579208923731619542357098500869e + 77 (c'est-à-dire un nombre avec 77 zéros)

La différence IS ÉNORME. Il y a 2389 zéros de différence entre eux. IL Y AURA DES COLLISIONS (une collision est un cas spécial lorsque deux fichiers d'entrée DIFFÉRENTS auront exactement le même hachage) puisque nous réduisons 10 ^ 2466 cas à 10 ^ 77 cas.

La seule façon de minimiser le risque de collision est d'agrandir l'espace de hachage et donc de rallonger les hahs. Idéalement, le hachage aura la longueur du fichier, mais c'est en quelque sorte idiot.


Le deuxième problème est la performance. Cela ne concerne que l'algorithme du hachage. Bien sûr, un hachage plus long nécessitera très probablement plus de cycles de processeur, mais un algorithme plus intelligent pourrait ne pas l'être. Je n'ai pas de réponse claire à cette question. C'est juste trop dur.

Cependant, vous pouvez comparer/mesurer différentes implémentations de hachage et en tirer des conclusions préliminaires.

Bonne chance ;)

8
Andrei Rînea

Il n'y a pas d'algorithme de hachage optimal unique. Si vous avez un domaine d'entrée connu, vous pouvez utiliser un générateur de hachage parfait tel que gperf pour générer un algorithme de hachage qui obtiendra un taux de 100% sur cet ensemble d'entrée particulier. Sinon, il n'y a pas de "bonne" réponse à cette question.

Le hashCode simple utilisé par la classe String de Java peut montrer un algorithme approprié.

Ci-dessous, l'implémentation "GNU Classpath". (Licence: GPL)

  /**
   * Computes the hashcode for this String. This is done with int arithmetic,
   * where ** represents exponentiation, by this formula:<br>
   * <code>s[0]*31**(n-1) + s[1]*31**(n-2) + ... + s[n-1]</code>.
   *
   * @return hashcode value of this String
   */
  public int hashCode()
  {
    if (cachedHashCode != 0)
      return cachedHashCode;

    // Compute the hash code using a local variable to be reentrant.
    int hashCode = 0;
    int limit = count + offset;
    for (int i = offset; i < limit; i++)
      hashCode = hashCode * 31 + value[i];
    return cachedHashCode = hashCode;
  }
3
activout.se

Vous pouvez obtenir les deux en utilisant la fonction de hachage Knuth décrite ici .

C'est extrêmement rapide en supposant une taille de table de hachage de puissance de 2 - juste une multiplication, un décalage et un bit et. Plus important encore (pour vous), il est excellent pour minimiser les collisions (voir cette analyse ).

D'autres bons algorithmes sont décrits ici .

2
Jason Cohen

"Murmurhash" est assez bon sur les performances et les collisions.

Le fil mentionné à "softwareengineering.stackexchange" a quelques tests et Murmur gagne.

J'ai écrit mon propre port C # de MurmurHash 2 sur .NET et l'ai testé sur une liste de 466k mots anglais, j'ai obtenu 22 collisions.

Les résultats et la mise en œuvre sont ici: https://github.com/jitbit/MurmurHash.net (avertissement, je suis impliqué dans ce projet open source!)

1
Alex

Voici une manière simple de l'implémenter vous-même: http://www.devcodenote.com/2015/04/collision-free-string-hashing.html

Voici un extrait du message:

si disons que nous avons un jeu de lettres majuscules en anglais, alors la longueur du jeu de caractères est 26 où A pourrait être représenté par le nombre 0, B par le nombre 1, C par le nombre 2 et ainsi de suite jusqu'à Z par le nombre 25. Maintenant, chaque fois que nous voulons mapper une chaîne de ce jeu de caractères à un nombre unique, nous effectuons la même conversion que nous l'avons fait dans le cas du format binaire

1
Abhishek Jain

J'adore Stackoverflow! La lecture de cette question m'a fait regarder un peu plus les fonctions de hachage et j'ai trouvé le Cuckoo Hash .

De l'article:

La recherche nécessite l'inspection de seulement deux emplacements dans la table de hachage, ce qui prend un temps constant dans le pire des cas (voir la notation Big O). Cela contraste avec de nombreux autres algorithmes de table de hachage, qui peuvent ne pas avoir de pire cas constant lié au moment de la recherche.

Je pense que cela correspond à vos critères de collisions et de performances. Il semble que le compromis soit que ce type de table de hachage ne peut être rempli qu'à 49%.

1
Jason Z