Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 26 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
26
Dung lượng
344,2 KB
Nội dung
Implémentation d'une table de hachage parallèle Raphaël Prévost | Wargan Solutions 19 juillet 2010 Résumé La table de hachage est une structure de données généralement utilisée pour implémenter tableaux associatifs, caches, ou plus généralement associer une clé une valeur Dans le cadre de notre projet, nous avons choisi de réaliser un cache de données reposant sur cette structure, particulièrement intéressante dans notre cas pour ses temps d'accès relativement constants par rapport au nombre d'entrées dans la table Cependant, la majorité des implémentations libres ne sont pas optimisées pour une utilisation parallèle Nous avons donc décidé de programmer cette structure nous-mêmes, en nous basant sur les travaux publiés sur le sujet Nous décrirons dans cet article le cheminement que nous avons suivi an de mener bien cette implémentation Première partie Dans l'intimité d'une table de hachage La table de hachage n'est pas une structure complexe : c'est en réalité un vulgaire tableau Tout l'art et la diculté de son implémentation se résume deux problèmes principaux : le premier consiste trouver une fonction injective (fonction de hachage) associant ecacement toute clé un emplacement quelconque dans le tableau Le second est plus subtil et procède directement du premier : notre tableau ne disposant que d'un nombre ni d'emplacements, il est nécessaire de gérer le cas où la fonction de hachage attribue le même emplacement plusieurs clés diérentes (collision) C'est le problème de la gestion de ces collisions qui est le plus ardu, et il existe de nombreuses faỗons de le traiter Nous allons les passer en revue concisément dans les paragraphes suivants Deux écoles principales : chaining contre open addressing Globalement, on peut diviser les algorithmes de résolution de collisions en deux familles principales, le chaining favorisant plutôt la montée en charge, et l'open addressing privilégiant une consommation mémoire réduite 1.1 Chaining Cet algorithme consiste régler eectivement la collision en stockant les deux paires clévaleur conictuelles dans le même emplacement Ceci est rendu possible en dénissant chaque élément du tableau comme le point d'entrée d'une liste chnée Cette méthode présente les avantages d'être simple implémenter et de conserver de bonnes performances même lorsque le tableau se remplit ; en eet, le temps requis pour retrouver une clé augmente linéairement avec le nombre de collisions Wargan Solutions | R&D En revanche, elle a tous les inconvénients des listes chnées : dommageable pour le cache du processeur, suppression des éléments avec une complexité linéaire, et consommation mémoire accrue par la necessité de stocker les pointeurs De nombreuses implémentations se basent sur cet algorithme, car il n'impose pas de redimensionner la table ; en eet, il est toujours possible d'y insérer une nouvelle clé au prix d'une certaine perte de performance 1.2 Open addressing L'alternative, open addressing , enregistre la clé conictuelle directement dans un emplacement disponible arbitraire dans le tableau Les coordonnées de cet autre emplacement sont calculées soit partir de l'emplacement où la clé aurait dû être insérée, soit en utilisant une autre fonction de hachage Quand le tableau se remplit, il peut être nécessaire d'eectuer ces calculs un certain nombre de fois avant de trouver un emplacement libre Cette méthode est plus ecace au niveau de la gestion de la mémoire, puisqu'il n'est pas nécessaire d'en allouer et que les données sont disposées idéalement pour le cache processeur Les performances se dégradent toutefois de manière très marquée dès que la table atteint un certain seuil de charge Les hybrides Il existe d'autres algorithmes empruntant certains principes propres aux deux méthodes présentées précédemment, en les combinant pour compenser leurs faiblesses respectives Nous nous sommes particulièrement intéressés ces derniers, dans le but de réaliser une implémentation la fois rapide et ecace 2.1 Cuckoo hashing 2.2 Cuckoo hashing et panier Cette méthode, dérivée de l'open addressing est exposée dans un article de recherche éponyme de Rasmus Pagh et Flemming Rodler publié en 2001 Le principe consiste utiliser deux fonctions de hachage, an de disposer de deux emplacements possibles pour chaque clé Lors de l'insertion d'une clé, si les deux emplacements sont occupés, l'algorithme va purement et simplement éjecter la clé déjà en place, comme un bébé coucou éjecte les ÷ufs du nid qu'il parasite, pour la remplacer par les nouvelles données La clé éjectée est ensuite réinsérée dans le tableau suivant le même algorithme On voit cependant que ce principe boucle inniment quand le tableau devient saturé Il est possible de retarder cet instant en utilisant un plus grand nombre de fonctions de hachage, augmentant ainsi le nombre d'emplacements potentiels Cet algorithme a pour principal intérêt d'orir des temps d'insertion et de lecture constants, ainsi qu'une bonne utilisation de la mémoire (dépendante toutefois du nombre de fonctions de hachage utilisées) Dans un article d'Adam Kirsh, Michael Mitzenmacher et Udi Wieder publié en 2008, l'idée est avancée de pallier les défauts du cuckoo hashing en adjoignant un panier la table de hachage, de faỗon recueillir les clộs tombộes du nid Ce panier se présente sous la forme d'un tableau secondaire de taille xe, dans lequel sont insérées temporairement les clés pour lesquelles un emplacement n'a pu être trouvé Ce procédé permet de retarder ecacement l'agrandissement de la table, sans utiliser plus de fonctions de hachage Wargan Solutions | R&D Finalement, le cuckoo hashing permet d'améliorer grandement les performances du modèle open addressing en orant des performances constantes en dépit du nombre d'entrées dans la table Son inconvénient majeur, l'impossibilité d'insérer de nouvelles clés au delà d'une certaine charge, peut être contourné en augmentant le nombre de fonctions de hachage et par l'adjonction d'un panier pour recueillir les clés problèmatiques Examinons maintenant les solutions permettant d'améliorer les algorithmes basés sur le chaining 2.3 Chaining interne et aggrégation Les améliorations apportées aux tables utilisant le chaining concernent d'avantage la consommation et la disposition des données en mémoire plutôt que les performances Une optimisation courante consiste inclure le premier maillon de la liste chnée directement dans l'emplacement réservé du tableau, an de permettre une meilleure utilisation du cache dans le cas où il n'y a pas de collisions Il est également possible de remplacer la liste par un tableau dynamique, an de préserver le cache du processeur et d'économiser la mémoire qui autrement serait utilisée pour stocker les pointeurs de la liste D'autres algorithmes vont plus loin et n'utilisent pas de réelles listes : les éléments sont directement placés dans le tableau lui-même, ce qui est appelé chaining interne Cette méthode se rapproche de l'open addressing , mais sans présenter l'inconvénient de calculer la position des emplacements alternatifs Deuxième partie Notre implémentation On a vu qu'en adoptant une approche hybride entre chaining et open addressing , il est possible de réaliser des algorithmes avec des performances et une occupation mémoire plus équilibrées Nous nous sommes donc eorcés d'eectuer une synthèse des diérents travaux que nous avons évoqué précédemment dans le but de trouver un compromis satisfaisant entre performances brutes et utilisation de la mémoire Le cuckoo hashing nous a particulièrement interressés du fait de ses temps d'accès constants ; nous l'avons donc utilisé comme base tout en cherchant pallier le problème des insertions impossibles dont cet algorithme est aigé quand la table devient trop pleine Algorithme de résolution des collisions La solution que nous avons retenue est, sans surprise, un cuckoo hashing adjoint d'un panier Le cuckoo hashing permet une bonne utilisation de l'espace de stockage de la table, tout en permettant un recyclage automatique des emplacements occupés par des clés supprimées Avec une fonction de hachage de qualité raisonnable, le panier reste en pratique de taille susamment réduite pour n'avoir qu'un impact négligeable sur les performances, tout en permettant une bien meilleure montée en charge Le panier statique exposé dans l'article que nous avons mentionné a cependant l'inconvénient de ne pas éliminer totalement le cas où une clé ne peut être enregistrée, surtout s'il est de petite taille Cela oblige parfois eectuer un agrandissement prématuré de la table, qui reste une opération coûteuse De grande taille, le panier rend ces redimensionnements forcés Wargan Solutions | R&D inexistants au prix d'un certain gaspillage mémoire Le panier dynamique nous est donc apparu la solution la plus performante Nous avons également cherché optimiser l'accès aux données du panier An d'éviter de devoir faire une recherche linéaire pour retrouver un élement placé dans le panier, nous avons eu recours un chaining interne entre l'emplacement où la clé aurait dû être insérée, et sa position réelle dans le panier Notre première version du panier utilisait un tableau dynamique, mais nous avons déterminé expérimentalement qu'une liste chnée apportait un gain de performances signicatif la fois lors de l'insertion et de la recherche grâce la diminution du nombre d'allocations mémoire Ce changement a néanmoins un inconvénient : dans le cas pathologique où tous les emplacements possibles du tableau sont occupés et déjà liés une place dans le panier, la recherche linéaire plus lente ne peut être évitée En pratique, cette situation est si rare que l'impact sur les performances est virtuellement nul Structure de la table de hachage Notre première implémentation se présentait sous la forme d'un tableau de structures contenant : La taille codée sur 16 bits de la clé Une taille nulle indique un emplacement supprimé L'index lui aussi codé sur 16 bits d'un éventuel doublon dans le panier Un pointeur sur un emplacement mémoire contenant la clé Celle-ci est directement préxée par un pointeur sur la valeur qui lui est associée Le panier était implémenté comme un tableau dynamique de ces mêmes structures, limité 65535 élements au maximum En pratique, cette limite ne devrait pas être atteinte (le panier ne comporte généralement pas plus d'une dizaine d'élements pour un million de couples clé-valeur) Cette disposition mémoire permettait de tirer partie du cache processeur, et d'avoir une surcharge mémoire raisonnable de 32 bits par clé pour une occupation mémoire totale de 64 bits par emplacement (96 bits sur architecture 64 bits) Après dierents tests, nous avons modié ce programme initial an d'améliorer les performances La table de hachage a été remplacée par un simple tableau de pointeurs, réduisant ainsi la taille des emplacements Les données ont été déplacées pour être stockées avec la clé dans un bloc mémoire dynamiquement alloué Le panier quant lui a été changé en une liste chnée, permettant ainsi une croissance illimitée Le chaining fut réimplémenté en conséquence pour utiliser un pointeur au lieu d'un index Ces modications ont amélioré les performances d'environ 20% en lecture et 10% en insertion, mais l'occupation mémoire excédentaire a augmenté de 32 bits par clé An de permettre une énumération aisée des éléments stockés dans notre table de hachage, nous avons ajouté un pointeur supplémentaire la structure contenant les données Ainsi, une liste chnée de tous les éléments est construite au gré des insertions, permettant de les énumérer dans l'ordre suivant lequel ils ont été insérés Cela nous a permis d'optimiser du même coup le redimensionnement de la table, en ne parcourant que les éléments de la liste au lieu de la table entière Grâce cette modication, l'insertion est 20% plus rapide Wargan Solutions | R&D Troisième partie Le dé du parallèlisme An de rendre l'accès cette table parallèlisable, nous lui avons ajouté un verrou de type lecteur/écrivain Ce type de verrou autorise plusieurs processus lire les données simultanément tant qu'aucun d'entre eux n'écrit dans la table Lorsqu'un processus écrit, tous les autres doivent attendre que l'insertion soit terminée avant de pouvoir accéder de nouveau la table Cette solution est simple mais peu satisfaisante ; en eet, rien ne justie de bloquer tous les processus lecteurs dans le cas où un processus écrivain modie une clé laquelle ils ne tentent pas d'accéder Nous avons donc cherché améliorer la granularité de la synchronisation en modiant la structure de la table Accès parallèles Nous avons implémenté notre table de hachage dans le but de l'utiliser au sein d'un serveur, c'est dire un environnement où toutes les ressources doivent être simultanément accessibles en parallèle par de nombreux processus Ce point est d'autant plus crucial que l'avènement des processeurs disposant de multiples c÷urs permet des gains de performance très importants pour peu qu'on utilise des structures favorisant un parallèlisme d'échelle Il n'était donc pas possible de se limiter au schéma de verrouillage primitif évoqué dans la partie précédente, car sa mauvaise granularité empêche sans légitimité les accès concurrents des éléments indépendants Nous avons choisi de modier la structure originale de notre table de hachage pour la diviser en segments, permettant d'obtenir une granularité de verrouillage plus ne de la synchronisation Le niveau le plus précis imposait d'attribuer un verrou chaque emplacement du tableau, ce qui nous a paru bien trop coûteux en occupation mémoire La segmentation permet d'augmenter le nombre d'accès concurrents moindre coût et d'ouvrir d'autres perspectives intéressantes que nous allons détailler Une table de hachage segmentée Plutôt que de rejeter notre implémentation initiale et de tout réécrire, nous avons choisi de redénir ce que nous appelions précédemment table de hachage, segment, et de construire ce que nous appellerons dans la suite de ce document table de hachage au dessus de la structure existante La nouvelle table de hachage est donc une collection de segments indépendants ; cela pose a priori un nouveau problème : comment décider dans quel segment chaque clé doit être insérée ? Il existe de nombreuses réponses cette question, comme les ltres de Bloom ou les tables de hachage multiples niveaux, qui nous ont paru d'une complexité indésirable dans ce contexte où les performances brutes sont primordiales Nous avons choisi la solution, plus simple, de composer la table de hachage d'un nombre xe de 256 segments, et de distribuer les clés dans ces derniers suivant leur somme CRC8 Le CRC8 est un algorithme de somme de contrôle permettant d'associer toute clé un entier codé sur bits, que nous utilisons ici comme index de segment Le CRC8 étant un calcul extrêment simple et rapide, cela permet de n'ajouter qu'un minimum de complexité l'algorithme existant Wargan Solutions | R&D Il aurait été possible d'économiser de la mémoire en utilisant un nombre dynamique de segments, mais cette solution impose de réinsérer toutes les clés enregistrées chaque ajout ou suppression d'un segment En eet, changer le nombre des ces derniers modie la distribution des clés Cette opération coûteuse verrouillerait de surcrt toute la table pendant son déroulement, allant l'encontre de notre objectif de parallèlisme Le coût mémoire de la solution retenue est plus important mais reste constant Nous allons voir que cette solution améliore également les performances générales de l'implémentation sous-jacente La dissolution des collisions L'utilisation de segments nous a permis de favoriser les accès concurrents aux données de la table de hachage, mais présente également une propriété intéressante En distribuant les clés suivant leur CRC8, cet algorithme diminue la probabilité de collision dans chaque segment, en allégeant leur charge respective et en opérant une première répartition pseudo-aléatoire Il en résulte des temps d'insertion et d'accès plus rapides du fait de la réduction du nombre de collisions, diluées par la présélection laquelle sont soumises les clés Au nal, l'implémentation complète présente des temps d'accès plus avantageux en utilisant plusieurs processus parallèles, ce qui correspond l'objectif que nous nous étions xé Dans le cas où la table ne doit être utilisée que par un unique processus, il reste toutefois plus avantageux d'utiliser un simple segment comme table de hachage an de réduire l'empreinte mémoire Wargan Solutions | R&D A Annexe : Quelques chires Notre implémentation nale a des performances compétitives ; insérer 800 000 clés est eectué en 0.800 secondes, les relire en 0.430 secondes sur un Intel Core Duo 2GHz Le remplacement de toutes les clés est un peu plus long, prenant 0.560 secondes La suppression est réalisée en 0.470 secondes À titre de comparaison, la bibliothèque hashlib de C B Falconer modiée an d'être synchronisée, utilisant la même fonction de hachage et les même données de test réalise l'insertion des 800 000 élements en 1.140 secondes, la relecture en 0.500 secondes et le remplacement en 0.700 secondes Une autre implémentation de référence, celle de Christopher Clark, insère les 800 000 clés en 0.750 secondes et les retrouve en 0.500 secondes, avec synchronisation Sans verrous, ses temps sont plus rapides : 0.690 secondes en insertion et 0.440 secondes en lecture Tous ces résultats ont été obtenus avec les mêmes options de compilation, la même fonction de hachage (lookup3 de Bob Jenkins) et le même échantillon de données Wargan Solutions | R&D B Annexe : Bibliographie Cuckoo Hashing, Pagh, Rodler, 2001 http ://citeseer.ist.psu.edu/pagh01cuckoo.html More Robust Hashing : Cuckoo Hashing with a Stash, Kirsch, Mitzenmacher, Wieder, 2008 http ://research.microsoft.com/users/uwieder/papers/stash-full.pdf Hash Functions and Block Ciphers, Jenkins http ://burtleburtle.net/bob/hash/ Wargan Solutions | R&D C Annexe : Implémentation /∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ Concrete Server ∗ ∗ C o p y r i g h t ( c ) 2005 − 2010 R a p h a e l P r e v o s t ∗ ∗ ∗ ∗ T h i s l i b r a r y i s f r e e s o f t w a r e ; y o u c a n r e d i s t r i b u t e i t and / o r ∗ ∗ m o d i f y i t u n d e r t h e t e r m s o f t h e GNU L e s s e r G e n e r a l P u b l i c ∗ ∗ L i c e n s e as p u b l i s h e d by t h e Free S o f t w a r e Foundation ; e i t h e r ∗ ∗ v e r s i o n o f t h e L i c e n s e , o r ( a t y o u r o p t i o n ) any l a t e r v e r s i o n ∗ ∗ ∗ ∗ This l i b r a r y i s d i s t r i b u t e d i n t h e hope t h a t i t w i l l be u s e f u l , ∗ ∗ b u t WITHOUT ANY WARRANTY; w i t h o u t e v e n t h e i m p l i e d w a r r a n t y o f ∗ ∗ MERCHANTABILITY o r FITNESS FOR A PARTICULAR PURPOSE S e e t h e GNU ∗ ∗ L e s s e r G e n e r a l P u b l i c L i c e n s e f o r more d e t a i l s ∗ ∗ ∗ ∗ You s h o u l d h a v e r e c e i v e d a c o p y o f t h e GNU L e s s e r G e n e r a l P u b l i c ∗ ∗ L i c e n s e a l o n g w i t h t h i s l i b r a r y ; i f not , w r i t e t o t h e Free S o f t w a r e ∗ ∗ F o u n d a t i o n , I n c , 51 F r a n k l i n S t r e e t , t h F l o o r , B o s t o n , MA 02110 − 1301 USA ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗/ 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 # # # #include # # # # # # # # # ifndef M_HASHTABLE_H define M_HASHTABLE_H ifdef _ENABLE_HASHTABLE ifdef " m_core_def h " FAST_TABLE define CACHE_HASHFNCOUNT /∗ number define CACHE_CUCKOORETRY /∗ retries define CACHE_GROWTHRATIO 1.6 /∗ threshold define CACHE_HASHFNCOUNT /∗ number define CACHE_CUCKOORETRY /∗ retries define CACHE_GROWTHRATIO 1.25 /∗ threshold /∗ ∗ typedef struct @defgroup cache to functions to bucket full grow the is m_cache pthread_rwlock_t ∗/ ∗/ table of hash if the to functions to bucket full grow is the table use (75%) { size_t _bucket_size ; size_t _bucket_count ; 45 char 46 char 47 void ∗∗ _ i n d e x ; ∗∗ _ b u c k e t ; ∗ _basket ; (∗ _freeval ) ( char 48 } m_cache ; void ∗) ; 50 51 /∗ ∗ 52 ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ 54 55 56 57 58 59 60 61 62 63 @ingroup @struct cache m_cache This structure supports Each key / v a l u e pair is the implementation stored in s i z e o f ( uint16_t ) + s i z e o f ( char @Note structure to use Even it simplistic the @ref if in this a context locking where scheme m_hashtable is this ∗) structure thread structure thread good and a with an safe hash overhead table of bytes safe , concurrency For of is it is concurrency , the not important , related it API is recommended due to better its to ∗/ ∗/ ∗ _lock ; 44 53 ∗/ use ∗/ module : : c a c h e 43 49 the endif 40 42 hash if else 39 41 of use ∗/ Wargan Solutions | R&D ∗ ∗/ 64 65 66 67 68 69 typedef struct m_cache } m_hashtable { ∗ _segment [ ] ; m_hashtable ; 70 71 /∗ −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ∗ / 72 73 public m_cache ∗ cache_alloc ( 74 75 /∗ public inline 78 79 /∗ public inline 82 83 /∗ public 86 void void void /∗ public /∗ public 95 const ∗ c a c h e _ a d d ( m_cache ∗ h , const inline void /∗ public 100 101 /∗ void l , ∗v ) ; size_t l , void ∗v ) ; void size_t len , ∗ key , char size_t len ) ; void ∗h , ∗ function ) ( c a c h e _ f o r e a c h ( m_cache int ( const ∗, char size_t , void ∗) ) ; −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ∗ / 98 99 ∗k , char const ∗ c a c h e _ f i n d ( m_cache ∗ h , 96 97 size_t −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ∗ / 93 94 ∗k , char −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ∗ / 91 92 const ∗ c a c h e _ p u s h ( m_cache ∗ h , void 89 90 ∗) ) ; ∗ c a c h e _ f i n d e x e c ( m_cache ∗ h , c h a r ∗ key , ∗(∗ f u n c t i o n ) ( ∗) ) ; 87 88 void −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ∗ / 84 85 ∗ freeval )( −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ∗ / 80 81 ( −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ∗ / 76 77 void void ∗ c a c h e _ p o p ( m_cache ∗ h , const char ∗ key , size_t len ) ; −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ∗ / 102 103 public m_cache ∗ c a c h e _ f r e e ( m_cache ∗ h ) ; 104 105 /∗ 106 /∗ 107 −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ∗ / Segmented h a s h t a b l e ∗/ / ∗ −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ∗ / 108 109 public m_hashtable 110 111 /∗ void ( ∗ freeval )( public inline 114 void /∗ public inline 119 size_t len , void /∗ const char ∗ key , ∗ val ) ; size_t len , const char ∗ key , ∗ val ) ; −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ∗ / 122 123 void ∗ h a s h t a b l e _ u p d a t e ( m_hashtable ∗h , 120 121 ∗) ) ; −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ∗ / 117 118 void ∗ h a s h t a b l e _ i n s e r t ( m_hashtable ∗h , 115 116 void −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ∗ / 112 113 ∗ hashtable_alloc ( public inline 124 void ∗ h a s h t a b l e _ r e m o v e ( m_hashtable ∗h , size_t len ) ; const char ∗ key , 125 126 /∗ −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ∗ / 127 128 public inline void ∗ h a s h t a b l e _ f i n d e x e c ( m_hashtable ∗h , 10 const char ∗ key , Wargan Solutions | R&D /∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ Concrete Server ∗ ∗ C o p y r i g h t ( c ) 2005 − 2010 R a p h a e l P r e v o s t ∗ ∗ ∗ ∗ T h i s l i b r a r y i s f r e e s o f t w a r e ; y o u c a n r e d i s t r i b u t e i t and / o r ∗ ∗ m o d i f y i t u n d e r t h e t e r m s o f t h e GNU L e s s e r G e n e r a l P u b l i c ∗ ∗ L i c e n s e as p u b l i s h e d by t h e Free S o f t w a r e Foundation ; e i t h e r ∗ ∗ v e r s i o n o f t h e L i c e n s e , o r ( a t y o u r o p t i o n ) any l a t e r v e r s i o n ∗ ∗ ∗ ∗ This l i b r a r y i s d i s t r i b u t e d i n t h e hope t h a t i t w i l l be u s e f u l , ∗ ∗ b u t WITHOUT ANY WARRANTY; w i t h o u t e v e n t h e i m p l i e d w a r r a n t y o f ∗ ∗ MERCHANTABILITY o r FITNESS FOR A PARTICULAR PURPOSE S e e t h e GNU ∗ ∗ L e s s e r G e n e r a l P u b l i c L i c e n s e f o r more d e t a i l s ∗ ∗ ∗ ∗ You s h o u l d h a v e r e c e i v e d a c o p y o f t h e GNU L e s s e r G e n e r a l P u b l i c ∗ ∗ L i c e n s e a l o n g w i t h t h i s l i b r a r y ; i f not , w r i t e t o t h e Free S o f t w a r e ∗ ∗ F o u n d a t i o n , I n c , 51 F r a n k l i n S t r e e t , t h F l o o r , B o s t o n , MA 02110 − 1301 USA ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗/ 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include # /∗ −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ∗ / ifdef /∗ " m_hashtable h" _ENABLE_HASHTABLE −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ∗ / 26 27 28 29 static /∗ crc8 lookup uint8_t table ( Maxim/ D a l l a s _crc_lut [ ] = wire ) ∗/ { x00 , x5e , xbc , xe2 , x61 , x3f , xdd , x83 , 30 xc2 , x9c , x7e , x20 , xa3 , xfd , x1f , x41 , 31 x9d , xc3 , x21 , x7f , xfc , xa2 , x40 , x1e , 32 x5f , x01 , xe3 , xbd , x3e , x60 , x82 , xdc , 33 x23 , x7d , x9f , xc1 , x42 , x1c , xfe , xa0 , 34 xe1 , xbf , x5d , x03 , x80 , xde , x3c , x62 , 35 xbe , xe0 , x02 , x5c , xdf , x81 , x63 , x3d , 36 x7c , x22 , xc0 , x9e , x1d , x43 , xa1 , xff , 37 x46 , x18 , xfa , xa4 , x27 , x79 , x9b , xc5 , 38 x84 , xda , x38 , x66 , xe5 , xbb , x59 , x07 , 39 xdb , x85 , x67 , x39 , xba , xe4 , x06 , x58 , 40 x19 , x47 , xa5 , xfb , x78 , x26 , xc4 , x9a , 41 x65 , x3b , xd9 , x87 , x04 , x5a , xb8 , xe6 , 42 xa7 , xf9 , x1b , x45 , xc6 , x98 , x7a , x24 , 43 xf8 , xa6 , x44 , x1a , x99 , xc7 , x25 , x7b , 44 x3a , x64 , x86 , xd8 , x5b , x05 , xe7 , xb9 , 45 x8c , xd2 , x30 , x6e , xed , xb3 , x51 , x0f , 46 x4e , x10 , xf2 , xac , x2f , x71 , x93 , xcd , 47 x11 , x4f , xad , xf3 , x70 , x2e , xcc , x92 , 48 xd3 , x8d , x6f , x31 , xb2 , xec , x0e , x50 , 49 xaf , xf1 , x13 , x4d , xce , x90 , x72 , x2c , 50 x6d , x33 , xd1 , x8f , x0c , x52 , xb0 , xee , 51 x32 , x6c , x8e , xd0 , x53 , x0d , xef , xb1 , 52 xf0 , xae , x4c , x12 , x91 , xcf , x2d , x73 , 53 xca , x94 , x76 , x28 , xab , xf5 , x17 , x49 , 54 x08 , x56 , xb4 , xea , x69 , x37 , xd5 , x8b , 55 x57 , x09 , xeb , xb5 , x36 , x68 , x8a , xd4 , 56 x95 , xcb , x29 , x77 , xf4 , xaa , x48 , x16 , 57 xe9 , xb7 , x55 , x0b , x88 , xd6 , x34 , x6a , 58 x2b , x75 , x97 , xc9 , x4a , x14 , xf6 , xa8 , 59 x74 , x2a , xc8 , x96 , x15 , x4b , xa9 , xf7 , 60 xb6 , xe8 , x0a , x54 , xd7 , x89 , x6b , x35 define _key ( b ) ((b) define _len ( b ) ( 61 62 63 64 65 }; # # /∗ if your arch + sizeof ( uint16_t ) ) ∗ ( ( uint16_t ∗) requires aligned (b) ) ) reads , you 12 can modify this macro ∗/ Wargan Solutions | R&D 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 # # # define _pad ( b ) ( define _ptr ( b ) ( void _val ( b ) define (( # # define # { # { 83 84 } 85 # 86 87 88 89 90 while define # { + + ∗) ) ) ) ( int while (0) (k) , ( l )) ; } while (0) ( uint16_t ) + _pad ( b ) ) ) = ( uint16_t ) + _pad ( b0 ) ) ) ( uint16_t ) + _pad ( b1 ) ) ) ; ( uint16_t ) + _pad ( b ) ) ) (p) ; } while (0) = \ \ \ ((b) ((b) sizeof + \ sizeof _clr_ptr ( b ) + p) + (0) = NULL ; } while (0) \ sizeof \ while + _pad ( b ) \ sizeof sizeof (0) ∗(( i n t ∗∗) } } ( uint16_t ) , b1 ) ( ( b1 ) _set_val ( b , ( uint16_t ) + _pad ( b ) ) ) ) \ + ( ( b0 ) ∗(( char ∗∗) { define 91 92 _cpy_ptr ( b0 , ( l ) ; sizeof p) ((b) = l ) \ ∗(( char ∗∗) ∗(( char ∗∗) 82 k, _set_ptr ( b , ( uint16_t ) \ sizeof + + l ) (b) ) _set_key ( b , ∗(( char ∗∗) { define _set_len ( b , memcpy ( ( b ) define ((b) ∗ ( ( uint16_t ∗) { sizeof \ ∗) ∗(( i n t ∗∗) define sizeof ∗ ( ( uint16_t ∗) ( b ) ) ) ∗ ( ( char ∗∗) ( ( b) + ( uint16_t ) + _pad ( b ) sizeof + ∗) ) ) ( int = (p) ; \ 93 94 95 96 97 98 99 100 101 102 /∗ −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ∗ / static inline uint32_t _hash ( { register union const void /∗ Bob Jenkins ' lookup3 uint32_t { #i f a , const hash b, function , ∗ ptr ; size_t && ( defined ( i386 ) 105 || || use u; it len , for uint32_t the /∗ internal /∗ needed || HASH_LITTLE_ENDIAN #d e f i n e HASH_BIG_ENDIAN #e l i f Mac Powerbook G4 && \ || d e f i n e d ( i486 ) || d e f i n e d ( vax ) || || \ \ ( d e f i n e d ( BYTE_ORDER) 110 && d e f i n e d ( BIG_ENDIAN) BYTE_ORDER == BIG_ENDIAN) 111 ( defined ( sparc ) 112 defined ( sel ) ) || || #d e f i n e HASH_LITTLE_ENDIAN 114 #d e f i n e HASH_BIG_ENDIAN && \ \ d e f i n e d (POWERPC) 113 || d e f i n e d ( mc68000 ) #e l s e 116 #d e f i n e HASH_LITTLE_ENDIAN 117 #d e f i n e HASH_BIG_ENDIAN 0 #e n d i f 119 #d e f i n e rot (x , k) ((( x) 122 #d e f i n e mix ( a , b, c) 123 { > (32 − (k) ) ) ) || \ ∗/ Wargan Solutions | R&D 131 132 #d e f i n e 133 { final (a , 134 c ^= b; c 135 a ^= c ; a 136 b ^= a; b 137 c ^= b; c 138 a ^= c ; a 139 b ^= a; b 140 c ^= b; c 141 b, c) \ \ −= −= −= −= −= −= −= rot (b ,14) ; \ rot (c ,11) ; \ rot (a ,25) ; \ rot (b ,16) ; \ rot (c , 4) ; \ rot (a ,14) ; \ rot (b ,24) ; \ } 142 143 /∗ 144 a set up = b = the c = internal ∗/ state x52661314 + ( ( uint32_t ) len ) + initval ; /∗ wo ni Lulu ∗/ 145 146 u ptr = data ; 147 148 #i f 149 if 150 151 152 153 (HASH_LITTLE_ENDIAN) const const ((u i & x3 ) == uint32_t #i f d e f 0) { ∗k = DEBUG ( const ∗) uint32_t /∗ data ; read 32− b i t chunks ∗/ ∗ k8 ; uint8_t #e n d i f 154 155 /∗ 156 while all but ( len last > 157 a += k [ ] ; 158 mix ( a , 159 160 len −= block : 12) b, aligned reads and affect 32 bits of (a , b, c) ∗/ { b += k [ ] ; c += k [ ] ; c) ; 12; k += 3; } 161 162 /∗ − handle the l a s t ( probably p a r t i a l ) block ∗ ∗ " k [ ] & x f f f f f f " a c t u a l l y r e a d s b e y o n d t h e end o f t h e s t r i n g , b u t ∗ t h e n masks o f f t h e p a r t i t ' s n o t a l l o w e d t o r e a d Because t h e ∗ s t r i n g i s a l i g n e d , t h e masked− o f f t a i l i s i n t h e same word a s t h e ∗ rest of the string E v e r y m a c h i n e w i t h memory p r o t e c t i o n I ' v e s e e n ∗ d o e s i t on word b o u n d a r i e s , s o i s OK w i t h t h i s But VALGRIND w i l l ∗ s t i l l c a t c h i t and c o m p l a i n The m a s k i n g t r i c k d o e s make t h e h a s h ∗ n o t i c a b l y f a s t e r f o r s h o r t s t r i n g s ( l i k e E n g l i s h words ) ∗/ 163 164 165 166 167 168 169 170 171 172 #i f n d e f DEBUG 173 174 switch ( len ) { 175 case 12: c += k [ ] ; 176 case 11: c += k [ ] & x f f f f f f ; 177 case 10: c += k [ ] & xffff ; 178 case : c += k [ ] & 0xff ; 179 case : b += k [ ] ; 180 case : b += k [ ] & x f f f f f f ; 181 case : b += k [ ] & xffff ; 182 case : b += k [ ] & 183 case : a += k [ ] ; 184 case : a += k [ ] & x f f f f f f ; 185 case : a += k [ ] & xffff ; 186 case : a += k [ ] & 0xff ; break ; 187 case : return /∗ zero length 188 c ; b += k [ ] ; a += k [ ] ; break ; b += k [ ] ; a += k [ ] ; b += k [ ] ; b += k [ ] ; a += k [ ] ; xff ; a += k [ ] ; a += k [ ] ; break ; break ; break ; break ; a += k [ ] ; a += k [ ] ; a += k [ ] ; break ; break ; break ; break ; break ; break ; strings require no mixing } 189 190 /∗ #e l s e 191 = ( make const 192 k8 193 switch ( len ) valgrind uint8_t ∗) happy ∗/ k; { 194 case 12: c += k [ ] ; 195 case 11: c += b += k [ ] ; ( ( uint32_t ) a += k [ ] ; k8 [ ] ) 14