web-dev-qa-db-fra.com

hachage de chaînes rapide, de grande largeur et non cryptographique en python

J'ai besoin d'une fonction de hachage de chaîne haute performance en python qui produise des entiers avec au moins 34 bits de sortie (64 bits auraient un sens, mais 32, c'est trop peu). Il y a plusieurs autres questions comme celle-ci sur Stack Overflow, mais parmi celles que j'ai trouvées, toutes les réponses acceptées/votées ont été classées dans l'une des catégories suivantes, qui ne s'appliquent pas (pour la raison indiquée).

  • Utilisez la fonction hash() intégrée. Cette fonction, au moins sur la machine pour laquelle je développe (avec Python 2.7 et un processeur 64 bits), produit un entier qui correspond à 32 bits - pas assez grand pour mes besoins.
  • Utilisez hashlib. hashlib fournit des routines de hachage cryptographiques, qui sont far plus lentes que nécessaire, à des fins non cryptographiques. Je trouve cela évident, mais si vous avez besoin de points de repère et de citations pour vous en convaincre, je peux vous le fournir.
  • Utilisez la fonction string.__hash__() comme prototype pour écrire votre propre fonction. Je pense que ce sera la bonne façon de faire, sauf que l'efficacité de cette fonction réside dans l'utilisation de la fonction c_mul, qui englobe environ 32 bits - encore une fois, trop petit pour mon utilisation! Très frustrant, c'est si proche de la perfection!

Une solution idéale aurait les propriétés suivantes, dans un ordre d'importance relativement lâche.

  1. Avoir une plage de sortie s'étendant sur au moins 34 bits de long, probablement 64 bits, tout en préservant les propriétés de consistent / avalanche sur all bits. (La concaténation de hachages 32 bits a tendance à violer les propriétés d'avalanche, du moins avec mes exemples stupides.)
  2. Portable. Étant donné la même chaîne d'entrée sur deux machines différentes, je devrais obtenir le même résultat les deux fois. Ces valeurs seront stockées dans un fichier pour une utilisation ultérieure.
  3. Haute performance. Le plus rapide sera le mieux car cette fonction sera appelée environ 20 milliards de fois au cours de l'exécution du programme que je suis en train d'exécuter (c'est le code essentiel à la performance pour le moment.) Il n'a pas besoin d'être écrit en C, c'est vraiment Il suffit de surpasser md5 (quelque part dans le domaine du hash () intégré pour les chaînes).
  4. Acceptez un entier 'perturbation' (quel est le meilleur mot à utiliser ici?) En tant qu'entrée pour modifier la sortie. Je donne un exemple ci-dessous (les règles de formatage de la liste ne me permettaient pas de le rapprocher). Je suppose que cela n’est pas nécessaire à 100%, car cela peut être simulé en perturbant manuellement la sortie de la fonction, mais son entrée me donne une belle sensation de chaleur.
  5. Entièrement écrit en Python. Si absolument, positivement a besoin d'être écrit en C, je suppose que cela peut être fait, mais je prendrais une fonction 20% plus lente écrite en python par rapport à la plus rapide en C, juste à cause de la coordination du projet mal de tête d'utiliser deux langues différentes. Oui, c'est une dérobade, mais c'est une liste de souhaits ici.

Exemple de hachage 'Perturbed', où la valeur de hachage est modifiée radicalement par un petit nombre entier n

def perturb_hash(key,n):
    return hash((key,n))

Enfin, si vous êtes curieux de savoir ce que je fais comme si j'avais besoin d'une telle fonction de hachage, je suis en train de réécrire complètement le module pybloom pour améliorer considérablement ses performances. J'ai réussi à cela (il tourne maintenant environ 4x plus vite et utilise environ 50% de l'espace), mais j'ai remarqué que parfois, si le filtre devenait suffisamment grand, il atteignait soudainement des taux de faux positifs. J'ai réalisé que c'était parce que la fonction de hachage ne traitait pas assez de bits. 32 bits ne peuvent traiter que 4 milliards de bits (attention, les adresses de filtre sont des bits et non des octets) et certains des filtres que j'utilise pour les données génomiques sont au moins deux ou plus (donc au moins 34 bits).

Merci!

37
eblume

Examinez la variante 128 bits de MurmurHash3 . La page de l'algorithme inclut certains numéros de performance. Il devrait être possible de porter cela en Python, pur ou en extension C. ( Updated l'auteur recommande d'utiliser la variante 128 bits et de jeter les bits dont vous n'avez pas besoin).

Si MurmurHash2 64 bits fonctionne pour vous, il existe une implémentation Python (extension C) dans le package pyfasthash , qui inclut quelques autres variantes de hachage non cryptographiques, bien que certaines d'entre elles n'offrent qu'une sortie 32 bits.

Update J'ai réalisé un wrapper Python rapide pour la fonction de hachage Murmur3. Le projet Github est ici et vous pouvez le trouver également dans Python Package Index ; il faut juste un compilateur C++ pour construire; pas de boost nécessaire.

Exemple d'utilisation et comparaison temporelle:

import murmur3
import timeit

# without seed
print murmur3.murmur3_x86_64('samplebias')
# with seed value
print murmur3.murmur3_x86_64('samplebias', 123)

# timing comparison with str __hash__
t = timeit.Timer("murmur3.murmur3_x86_64('hello')", "import murmur3")
print 'murmur3:', t.timeit()

t = timeit.Timer("str.__hash__('hello')")
print 'str.__hash__:', t.timeit()

Sortie:

15662901497824584782
7997834649920664675
murmur3: 0.264422178268
str.__hash__: 0.219163894653
22
samplebias

Utilisez la fonction hash () intégrée. Cette fonction, du moins sur la machine pour laquelle je développe (avec Python 2.7 et un processeur 64 bits) produit un entier qui tient dans les 32 bits - pas assez grand pour Mes objectifs. .

Ce n'est pas vrai. La fonction de hachage intégrée générera un hachage 64 bits sur un système 64 bits.

Voici la fonction de hachage str de python de Objects/stringobject.c (version 2.7 de Python):

static long
string_hash(PyStringObject *a)
{
    register Py_ssize_t len;
    register unsigned char *p;
    register long x;      /* Notice the 64-bit hash, at least on a 64-bit system */

    if (a->ob_shash != -1)
    return a->ob_shash;
    len = Py_SIZE(a);
    p = (unsigned char *) a->ob_sval;
    x = *p << 7;
    while (--len >= 0)
        x = (1000003*x) ^ *p++;
    x ^= Py_SIZE(a);
    if (x == -1)
        x = -2;
    a->ob_shash = x;
    return x;
}
3
Saish

"chaînes": je suppose que vous souhaitez hacher les objets Python 2.x str et/ou Python3.x bytes et/ou bytearray.

Cela peut violer votre première contrainte, mais: envisagez d'utiliser quelque chose comme 

(zlib.adler32(strg, perturber) << N) ^ hash(strg)

pour obtenir un hachage de (32 + N) bits.

2
John Machin

Si vous pouvez utiliser Python 3.2, le résultat de hachage sous Windows 64 bits est désormais une valeur 64 bits. 

0
casevh