Projet 3 - Implémentation et analyse des tables de hachage Kabbali Rizlaine 10 mai 2020 Contents 1 Introduction 2 2 Méthodes 2 2.1 Choix d’implémentation pour les algorithmes de hachage et de gestion de collision . . . . . . 2 2.1.1 Remplacement des valeurs supprimées par des valeurs sentinelles dans le double hachage 2 2.1.2 Garantir un résultat impair pour h2 (key) . . . . . . . . . . . . . . . . . . . . . . . . . 2 2.2 Choix d’implémentation pour les tests et les graphiques . . . . . . . . . . . . . . . . . . . . . 2 3 Résultats 3.1 Vitesse d’insertion et de suppression dans une table avec gestion de collision par chaı̂nage . . 3.1.1 Insertion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.1.2 Suppression . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.2 Vitesse d’insertion et de suppression dans une table avec gestion de collision par adressage ouvert (double hachage) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.2.1 Insertion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.2.2 Suppression . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.3 Nombre de sondages moyen à l’insertion et à la suppression . . . . . . . . . . . . . . . . . . . 3.4 Nombre de collisions en fonction de l’algorithme de hachage utilisé . . . . . . . . . . . . . . . 3.4.1 Hachage de 10000 clés composées des 26 lettres de l’alphabet dans un tableau de 128 emplacements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.4.2 Hachage de 10000 clés composées de 93 caractères UNICODE dans un tableau de 128 emplacements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.4.3 Hachage de 10000 clés composées de 93 caractères UNICODE dans un tableau de 1024 emplacements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 3 3 3 4 Discussion sur l’hypothèse de hachage uniforme 8 5 Conclusion 8 1 5 5 5 6 6 6 7 7 1 Introduction Il nous a été demandé dans ce travail d’implémenter différents algorithmes de hachage et de tester leur efficacité afin de comprendre l’impact qu’ont certains paramètres sur celle-ci. 2 2.1 Méthodes Choix d’implémentation pour les algorithmes de hachage et de gestion de collision J’ai commencé par implémenter les méthodes de hachage telles que décrites dans le pseudo-code de l’énoncé, mais suite à quelques tests et lectures, j’y ai ajouté quelques optimisations. 2.1.1 Remplacement des valeurs supprimées par des valeurs sentinelles dans le double hachage Lorsqu’une valeur est supprimée, elle est remplacée par une valeur sentinelle, qui permet de savoir que cette place a été occupée à un certain moment. De cette manière, lorsqu’on utilise la méthode self.get(key), celle-ci peut continuer à chercher lorsqu’elle tombe sur cette valeur sentinelle et s’arrêter pour renvoyer une KeyError dès qu’elle tombe sur un emplacement vierge. De plus, afin d’optimiser les futures utilisations de la méthode get(key) ou delete(key), lorsque l’on rencontre une valeur sentinelle avant de trouver la clé désirée, celles-ci échangent leurs places. 2.1.2 Garantir un résultat impair pour h2 (key) Comme nous l’avons vu au cours théorique, pour que la méthode de gestion de collisions par double hachage donne de bons résultats, h2(key) et la longueur de la table doivent être premiers entre eux. Une manière d’y arriver est de faire en sorte que h2(key) retourne un chiffre impair et que la table soit une puissance de deux. Pour cela, je me suis tout simplement servie d’un or 0b1, qui garantit donc que le dernier bit du résultat sera un 1 et qu’il sera donc impair. Une autre possibilité aurait été de choisir un m premier et de faire en sorte que h2(key) renvoie une valeur inférieure à m avec un modulo m, mais je ne m’attarderai pas sur cette implémentation que je n’ai pas choisie, n’étant pas celle présentée au cours. 2.2 Choix d’implémentation pour les tests et les graphiques J’ai tenté d’écrire mes fonctions de timer et de manière à pouvoir tester mes méthodes avec différents jeux de données afin de pouvoir déceler les limites des algorithmes de hachage implémentés. Je présenterai ici les résultats des tests qui m’ont paru les plus pertinents. De plus, j’ai décidé de tenir compte des temps minimums que donne la fonction timeit.timeit afin de négliger au maximum les biais dûs à la rapidité du processeur. Pour certaines figures, j’ai choisi de prendre des graphiques dont les courbes individuelles des algorithmes ne sont pas distinguables. Je pars du postulat que l’intérêt ici est de voir la tendance générale et pas le comportement d’un algorithme en particulier, sauf s’il s’en éloigne de manière remarquable. 2 3 3.1 3.1.1 Résultats Vitesse d’insertion et de suppression dans une table avec gestion de collision par chaı̂nage Insertion La figure 1 nous montre que le temps d’insertion dans une table dont les collisions sont gérées par liste chaı̂née est relativement constant. Nous pouvons donc considérer que la complexité de la méthode d’insertion est en O(1). L’algorithme de hachage CRC32 semble être le moins performant des trois, quel que soit le jeu de données. Les trois algorithmes sont très légèrement plus performant lorsque la taille de la table est une puissance de deux. Figure 1: Vitesse d’insertion de 128 éléments dans un tableau de 128 places 3.1.2 Suppression Figure 2: Vitesse de suppression de 128 éléments dans un tableau de 128 places 3 Malgré les quelques pics subis par les algorithmes CRC32 et KR la figure 2 nous amène aux mêmes conclusions que pour l’insertion. En effet, si α = O(1), dans le cas d’algorithmes de hachage uniforme, la suppression est en O(1). La figure suivante nous confirme que la suppression n’est en O(1) que si α = O(1). En effet, nous pouvons voir qu’une fois que le nombre d’éléments dépasse le nombre de place, la vitesse de suppression augmente proportionnellement au facteur de chargement. La suppression est dans ce cas en O(1+α) Figure 3: Vitesse de suppression de 3000 éléments dans un tableau de 103 places 4 3.2 3.2.1 Vitesse d’insertion et de suppression dans une table avec gestion de collision par adressage ouvert (double hachage) Insertion Dans la figure 4, nous pouvons constater que le temps d’insertion grandit avec le load factor. On peut en déduire qu’il est en lien avec le nombre de sondages à effectuer avant de trouver une place, qui lui-même 1 correspondant à l’espérance augmente avec le load factor. En effet, ce graphe à l’allure de la fonction α1 ln 1−α du nombre de sondage pour une recherche. Figure 4: Vitesse d’insertion de 128 éléments dans un tableau de 128 places 3.2.2 Suppression La suppression ne dépend pas du load factor au moment de la suppression, mais de celui au moment où la clé à été insérée. En effet, si nous supprimons les clés du tableau de manière aléatoire (ce qui est le plus proche de conditions réelles), la tendance du graphique est quelconque (voir figure ??). Par contre, si nous enlevons les clés dans l’ordre où ils ont été insérés, on retrouve l’inverse du graphe d’insertion (voir figure 5). En effet, les premiers insérés l’ont été rapidement, grâce à un load factor faible, seront les premiers à être supprimés, et donc le seront également rapidement, et ce malgré un load factor égal à un. Figure 5: Vitesse de suppression de 128 éléments dans un tableau de 128 places 5 3.3 Nombre de sondages moyen à l’insertion et à la suppression Les tests du nombre moyen de sondage ont été faits avec 1000 insertions sur un tableau de 128 emplacements (Table 1a) et avec autant de suppressions qu’il y avait d’éléments dans un tableau de 1024 (Table 1b). Les valeurs obtenues tendent vers les valeurs théoriques pour toutes les combinaisons de DoubleHasher sauf pour ceux, en jaune dans le tableau 1b dont le premier hasher est l’algorithme de Kernighan et Ritchie. Algorithme—Load factor Hasher Crc32+ i Hasher Crc32 Hasher Crc32+ i Hasher KR Hasher Crc32+ i Hasher Djb2 Hasher KR+ i Hasher Crc32 Hasher KR+ i Hasher KR Hasher KR+ i Hasher Djb2 Hasher Djb2+ i Hasher Crc32 Hasher Djb2+ i Hasher KR Hasher Djb2+ i Hasher Djb2 Valeurs théoriques 0.5 2.064 2.057 2.059 2.150 2.050 2.044 2.023 2.027 2.252 2.000 0.9 11.908 9.814 9.977 10.297 9.636 11.032 9.922 9.593 10.846 10.000 Algorithme—Load factor Hasher Crc32+ i Hasher Crc32 Hasher Crc32+ i Hasher KR Hasher Crc32+ i Hasher Djb2 Hasher KR+ i Hasher Crc32 Hasher KR+ i Hasher KR Hasher KR+ i Hasher Djb2 Hasher Djb2+ i Hasher Crc32 Hasher Djb2+ i Hasher KR Hasher Djb2+ i Hasher Djb2 Valeurs théoriques (a) Nombre moyen de sondages à l’insertion 3.4 0.5 1.406 1.334 1.355 1.877 2.193 1.885 1.414 1.342 1.414 1.387 0.9 2.817 2.705 2.486 3.378 4.192 3.557 2.550 2.719 2.680 2.559 (b) Nombre moyen de sondages à la suppression Nombre de collisions en fonction de l’algorithme de hachage utilisé Pour évaluer le nombre de collisions, j’ai établi des graphiques représentant le nombre de clé par emplacements dans une table avec listes chaı̂nées. Les résultats diffèrent fort selon le jeu de données utilisé. Voici 3 cas intéressants à développer: 3.4.1 Hachage de 10000 clés composées des 26 lettres de l’alphabet dans un tableau de 128 emplacements Dans la figure 6, nous pouvons voir que le nombre de collisions de l’algorithme KR présente de très fortes variations alors que les courbes des deux autres algorithmes sont relativement constantes. Figure 6: uniformité des algorithmes avec des clés composées des 26 lettres de l’alphabet 6 3.4.2 Hachage de 10000 clés composées de 93 caractères UNICODE dans un tableau de 128 emplacements Avec une variété plus importante de caractères pour former les clés, l’algorithme KR tend également vers une moyenne constante (voir figure 7). Les deux autres courbes restent constantes. Figure 7: uniformité des algorithmes avec des clés composées de 93 caractères différents 3.4.3 Hachage de 10000 clés composées de 93 caractères UNICODE dans un tableau de 1024 emplacements Avec une table plus grande, KR retrouve l’allure de la figure 6. Figure 8: uniformité des algorithmes avec des clés composées de 93 caractères différents 7 4 Discussion sur l’hypothèse de hachage uniforme Les résultats présentés ci-dessus confirment l’hypothèse de hachage uniforme dans la plupart des cas. Nous pouvons cependant constater que les résultats avec l’algorithme KR sont moins satisfaisants dans certaines conditions. Il semble notamment bien moins uniforme que Crc32 et Djb2 avec des clés de dix caractères choisis parmi les 26 lettres de l’alphabet, générées de manière aléatoire (Figure 6). Cela s’explique aisément par le fonctionnement de l’algorithme. Celui-ci n’étant que la simple addition du code Unicode des différents caractères de la clés. Il y a donc non seulement de nombreuses combinaisons qui peuvent atteindre le même index, mais aussi tous les anagrammes. La figure 8 et la table 1b montrent également un écartement des valeurs théoriques pour l’algorithme KR dans de grandes tables de hachage. Cet écart s’amenuise avec un nombre plus important de caractères par clé, comme le montre la figure 9. Mais cela représente alors un temps important d’exécution. Figure 9: uniformité des algorithmes avec des clés de 153 caractères L’uniformité des deux autres fonctions de hachage n’est bien sûr pas parfaite avec un écart maximal de 80 clés par rapport à notre référence de hachage uniforme. Néanmoins, à l’échelle d’un nombre aussi important de clés, nous pouvons considérer cette différence comme négligeable. 5 Conclusion Par ce travail, j’ai pu approfondir mes connaissances sur les méthodes de hachages et découvrir l’importance de non seulement choisir un bon algorithme de hachage, mais surtout de vérifier les circonstances pour lesquelles il est plus ou moins efficace. L’algorithme KR par exemple, est intéressant car rapide, mais il faut que les données y soient adaptées. La difficulté principale que j’ai éprouvée est le manque de temps et des contraintes trop rigides, comme la structure donnée et le nombre de pages, qu’il m’a d’ailleurs été difficile de respecter, préférant privilégier une analyse de qualité et des graphiques lisibles. 8