forked from menault/2026_TD1_DEV51_Qualite_Algo
Remplacement de la recherche dichotomique par une table de hachage
This commit is contained in:
+47
-26
@@ -26,6 +26,8 @@
|
||||
#define NB_MOTS_VOCAB 7000
|
||||
#define TAILLE_MOT 32
|
||||
#define MAX_MOTS_UNIQUES 20000
|
||||
/* Toujours moins de 50 % de cases occupées, même au maximum de mots. */
|
||||
#define TAILLE_HACHAGE (2 * MAX_MOTS_UNIQUES + 1)
|
||||
#define GRAINE 20261006UL
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
@@ -235,44 +237,54 @@ int est_mot_vide(const char *mot)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Le tableau reste trié alphabétiquement pendant le comptage.
|
||||
* Renvoie l'indice du mot, ou -(position d'insertion + 1) s'il est absent. */
|
||||
int chercher_mot(Entree *table, int nb_entrees, const char *mot)
|
||||
unsigned int hacher_mot(const char *mot)
|
||||
{
|
||||
int debut = 0;
|
||||
int fin = nb_entrees - 1;
|
||||
|
||||
while (debut <= fin) {
|
||||
int milieu = debut + (fin - debut) / 2;
|
||||
int comparaison = comparer(table[milieu].mot, mot);
|
||||
if (comparaison == 0)
|
||||
return milieu;
|
||||
if (comparaison < 0)
|
||||
debut = milieu + 1;
|
||||
else
|
||||
fin = milieu - 1;
|
||||
unsigned int h = 2166136261u;
|
||||
for (int i = 0; mot[i] != '\0'; i++) {
|
||||
h ^= (unsigned char)mot[i];
|
||||
h *= 16777619u;
|
||||
}
|
||||
return -(debut + 1);
|
||||
return h;
|
||||
}
|
||||
|
||||
void compter_mot(Entree *table, int *nb_entrees, const char *mot)
|
||||
/* L'index contient indice + 1 ; zéro désigne une case libre.
|
||||
* Renvoie l'indice du mot, ou -(case libre + 1) s'il est absent. */
|
||||
int chercher_mot(const Entree *table, const int *index_mots, const char *mot)
|
||||
{
|
||||
int indice = chercher_mot(table, *nb_entrees, mot);
|
||||
int position = hacher_mot(mot) % TAILLE_HACHAGE;
|
||||
|
||||
while (index_mots[position] != 0) {
|
||||
int indice = index_mots[position] - 1;
|
||||
if (egales(table[indice].mot, mot))
|
||||
return indice;
|
||||
/* Collision : essayer la case suivante, avec retour au début. */
|
||||
position = (position + 1) % TAILLE_HACHAGE;
|
||||
}
|
||||
return -(position + 1);
|
||||
}
|
||||
|
||||
void compter_mot(Entree *table, int *nb_entrees, int *index_mots, const char *mot)
|
||||
{
|
||||
int indice = chercher_mot(table, index_mots, mot);
|
||||
if (indice >= 0) {
|
||||
table[indice].occurrences++;
|
||||
} else {
|
||||
indice = -indice - 1;
|
||||
/* Décaler uniquement lors de la première apparition d'un mot. */
|
||||
for (int i = *nb_entrees; i > indice; i--)
|
||||
table[i] = table[i - 1];
|
||||
if (*nb_entrees >= MAX_MOTS_UNIQUES) {
|
||||
fprintf(stderr, "Trop de mots distincts (maximum : %d).\n",
|
||||
MAX_MOTS_UNIQUES);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
int position = -indice - 1;
|
||||
indice = *nb_entrees;
|
||||
copier(table[indice].mot, mot);
|
||||
table[indice].occurrences = 1;
|
||||
index_mots[position] = indice + 1;
|
||||
(*nb_entrees)++;
|
||||
}
|
||||
}
|
||||
|
||||
void compter_mots_ligne(Entree *table, int *nb_entrees, const char *ligne,
|
||||
long *nb_mots_total)
|
||||
void compter_mots_ligne(Entree *table, int *nb_entrees, int *index_mots,
|
||||
const char *ligne, long *nb_mots_total)
|
||||
{
|
||||
char mot[TAILLE_MOT];
|
||||
int i = 0;
|
||||
@@ -290,7 +302,7 @@ void compter_mots_ligne(Entree *table, int *nb_entrees, const char *ligne,
|
||||
if (k > 0) {
|
||||
(*nb_mots_total)++;
|
||||
if (!est_mot_vide(mot))
|
||||
compter_mot(table, nb_entrees, mot);
|
||||
compter_mot(table, nb_entrees, index_mots, mot);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -428,11 +440,20 @@ int main(void)
|
||||
normaliser_ligne(lignes[i]);
|
||||
|
||||
Entree *table = malloc(MAX_MOTS_UNIQUES * sizeof(Entree));
|
||||
int *index_mots = calloc(TAILLE_HACHAGE, sizeof(int));
|
||||
if (table == NULL || index_mots == NULL) {
|
||||
fprintf(stderr, "Allocation du tableau de comptage impossible.\n");
|
||||
free(table);
|
||||
free(index_mots);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
int nb_entrees = 0;
|
||||
long nb_mots_total = 0;
|
||||
for (int i = 0; i < nb_lignes; i++)
|
||||
compter_mots_ligne(table, &nb_entrees, lignes[i], &nb_mots_total);
|
||||
compter_mots_ligne(table, &nb_entrees, index_mots, lignes[i], &nb_mots_total);
|
||||
|
||||
/* Le tri déplace les entrées : l'index ne sert que pendant le comptage. */
|
||||
free(index_mots);
|
||||
trier_entrees(table, nb_entrees);
|
||||
|
||||
double moyenne = longueur_moyenne(table, nb_entrees);
|
||||
|
||||
@@ -219,3 +219,64 @@ real 0m1.251s
|
||||
user 0m1.214s
|
||||
sys 0m0.004s
|
||||
```
|
||||
|
||||
## Remplacement de la dichotomie par une table de hachage
|
||||
|
||||
Un index de 40 001 cases associe chaque mot à son entrée dans le tableau.
|
||||
`hacher_mot` calcule un entier à partir des caractères du mot ; le modulo
|
||||
donne la première case à examiner. Chaque case contient l'indice de
|
||||
l'entrée + 1, ou zéro si elle est libre.
|
||||
|
||||
En cas de collision, `chercher_mot` vérifie le mot avec `egales()` puis
|
||||
avance à la case suivante, en revenant au début si nécessaire. Deux mots
|
||||
ayant le même hachage restent donc bien distincts. Les nouveaux mots sont
|
||||
ajoutés à la fin du tableau, sans décaler les entrées existantes.
|
||||
|
||||
La recherche et l'insertion sont en O(1) en moyenne pour ces mots de
|
||||
longueur bornée, avec un pire cas en O(n) en cas de nombreuses collisions.
|
||||
La limite de 20 000 mots distincts est vérifiée avant chaque nouvelle
|
||||
insertion ; avec 40 001 cases, l'index reste rempli à moins de 50 %.
|
||||
|
||||
Ce choix ajoute `40 001 * sizeof(int)` octets, soit environ 156 Kio sur
|
||||
cette machine. L'index est libéré après le comptage, avant le tri final
|
||||
qui déplace les entrées. L'empreinte du rapport est conservée, mais cette
|
||||
version utilise davantage de mémoire pendant le comptage.
|
||||
|
||||
Profil enregistré dans [out/profil_apres_hachage.txt](out/profil_apres_hachage.txt).
|
||||
La comparaison porte sur la version précédente avec dichotomie et moyenne
|
||||
calculée une seule fois, conservée dans `out/profil_apres_moyenne.txt`.
|
||||
|
||||
| Mesure | Dichotomie | Hachage |
|
||||
| --- | ---: | ---: |
|
||||
| Appels de `chercher_mot` | 150 182 | 150 182 |
|
||||
| Comparaisons de chaînes dans `chercher_mot` | 1 718 447 (`comparer`) | 153 101 (`egales`) |
|
||||
| Temps réel médian sans profiling (`gcc -O0`, 5 exécutions par version) | 0,888 s | 0,831 s |
|
||||
|
||||
Les recherches font environ 11,2 fois moins de comparaisons. Le temps
|
||||
total baisse d'environ 6,4 % : le gain reste limité car la normalisation
|
||||
et le tri occupent désormais l'essentiel du temps. L'ordre d'arrivée des
|
||||
mots remplace l'ordre alphabétique pendant le comptage ; le tri final
|
||||
effectue donc un nombre différent de comparaisons, tout en produisant
|
||||
le même classement.
|
||||
|
||||
Les cinq mesures, réalisées en alternant les deux versions sur la même
|
||||
machine, sont respectivement : 0,883 / 0,892 / 0,888 / 0,883 / 0,888 s
|
||||
avant, et 0,831 / 0,830 / 0,831 / 0,832 / 0,829 s après.
|
||||
|
||||
Validation : compilation sans avertissement avec `-Wall -Wextra -Wpedantic`,
|
||||
rapport et sortie console identiques octet par octet sur les dix
|
||||
exécutions et avec `-pg`. L'empreinte reste **`1d53de903efd2b7c`**.
|
||||
Des tests temporaires sous AddressSanitizer et UndefinedBehaviorSanitizer
|
||||
ont vérifié les collisions avec retour à la première case, les doublons,
|
||||
les mots de 0/1/31 caractères, 10 000 comptages comparés à une référence
|
||||
linéaire, les 20 000 entrées possibles et le refus d'un dépassement.
|
||||
Le programme complet a également été exécuté avec ces deux outils,
|
||||
sans erreur et avec une sortie identique.
|
||||
|
||||
Commandes pour reproduire ce profil :
|
||||
|
||||
```bash
|
||||
mkdir -p out/apres_hachage
|
||||
gcc -O0 -pg analyseur.c -o out/apres_hachage/analyseur
|
||||
(cd out/apres_hachage && ./analyseur && gprof -b ./analyseur gmon.out > ../profil_apres_hachage.txt)
|
||||
```
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,172 @@
|
||||
Flat profile:
|
||||
|
||||
Each sample counts as 0.01 seconds.
|
||||
% cumulative self self total
|
||||
time seconds seconds calls ms/call ms/call name
|
||||
61.64 0.45 0.45 1599863 0.00 0.00 longueur
|
||||
10.27 0.53 0.07 12172087 0.00 0.00 comparer
|
||||
9.59 0.59 0.07 1 70.00 195.00 trier_entrees
|
||||
6.85 0.65 0.05 17202045 0.00 0.00 doit_echanger
|
||||
5.48 0.69 0.04 250000 0.00 0.00 est_mot_vide
|
||||
2.05 0.70 0.01 12198772 0.00 0.00 egales
|
||||
1.37 0.71 0.01 150182 0.00 0.00 chercher_mot
|
||||
1.37 0.72 0.01 1 10.00 10.00 decouper_lignes
|
||||
1.37 0.73 0.01 _init
|
||||
0.00 0.73 0.00 5297256 0.00 0.00 est_lettre
|
||||
0.00 0.73 0.00 1266663 0.00 0.00 en_minuscule
|
||||
0.00 0.73 0.00 928117 0.00 0.00 alea
|
||||
0.00 0.73 0.00 150182 0.00 0.00 compter_mot
|
||||
0.00 0.73 0.00 150182 0.00 0.00 hacher_mot
|
||||
0.00 0.73 0.00 32667 0.00 0.00 copier
|
||||
0.00 0.73 0.00 26801 0.00 0.00 ajouter
|
||||
0.00 0.73 0.00 21746 0.00 0.00 compter_mots_ligne
|
||||
0.00 0.73 0.00 21746 0.00 0.02 normaliser_ligne
|
||||
0.00 0.73 0.00 1 0.00 0.00 analyser_bigrammes
|
||||
0.00 0.73 0.00 1 0.00 1.65 construire_rapport
|
||||
0.00 0.73 0.00 1 0.00 1.65 ecart_type_longueur
|
||||
0.00 0.73 0.00 1 0.00 0.00 empreinte
|
||||
0.00 0.73 0.00 1 0.00 0.00 generer_texte
|
||||
0.00 0.73 0.00 1 0.00 5.89 generer_vocabulaire
|
||||
0.00 0.73 0.00 1 0.00 1.65 longueur_moyenne
|
||||
0.00 0.73 0.00 1 0.00 0.00 racine
|
||||
|
||||
Call graph
|
||||
|
||||
|
||||
granularity: each sample hit covers 4 byte(s) for 1.37% of 0.73 seconds
|
||||
|
||||
index % time self children called name
|
||||
<spontaneous>
|
||||
[1] 98.6 0.00 0.72 main [1]
|
||||
0.00 0.44 21746/21746 normaliser_ligne [3]
|
||||
0.07 0.12 1/1 trier_entrees [4]
|
||||
0.00 0.07 21746/21746 compter_mots_ligne [7]
|
||||
0.01 0.00 1/1 decouper_lignes [12]
|
||||
0.00 0.01 1/1 generer_vocabulaire [15]
|
||||
0.00 0.00 1/1 construire_rapport [16]
|
||||
0.00 0.00 1/1 longueur_moyenne [18]
|
||||
0.00 0.00 1/1 ecart_type_longueur [17]
|
||||
0.00 0.00 1/1 generer_texte [26]
|
||||
0.00 0.00 1/1 racine [27]
|
||||
0.00 0.00 1/1 analyser_bigrammes [24]
|
||||
0.00 0.00 1/1 empreinte [25]
|
||||
-----------------------------------------------
|
||||
0.00 0.00 5866/1599863 longueur_moyenne [18]
|
||||
0.00 0.00 5866/1599863 ecart_type_longueur [17]
|
||||
0.01 0.00 26801/1599863 ajouter [14]
|
||||
0.44 0.00 1561330/1599863 normaliser_ligne [3]
|
||||
[2] 61.6 0.45 0.00 1599863 longueur [2]
|
||||
-----------------------------------------------
|
||||
0.00 0.44 21746/21746 main [1]
|
||||
[3] 60.2 0.00 0.44 21746 normaliser_ligne [3]
|
||||
0.44 0.00 1561330/1599863 longueur [2]
|
||||
0.00 0.00 1539584/5297256 est_lettre [19]
|
||||
0.00 0.00 1266663/1266663 en_minuscule [20]
|
||||
-----------------------------------------------
|
||||
0.07 0.12 1/1 main [1]
|
||||
[4] 26.7 0.07 0.12 1 trier_entrees [4]
|
||||
0.05 0.07 17202045/17202045 doit_echanger [5]
|
||||
-----------------------------------------------
|
||||
0.05 0.07 17202045/17202045 trier_entrees [4]
|
||||
[5] 17.1 0.05 0.07 17202045 doit_echanger [5]
|
||||
0.07 0.00 12172087/12172087 comparer [6]
|
||||
-----------------------------------------------
|
||||
0.07 0.00 12172087/12172087 doit_echanger [5]
|
||||
[6] 10.3 0.07 0.00 12172087 comparer [6]
|
||||
-----------------------------------------------
|
||||
0.00 0.07 21746/21746 main [1]
|
||||
[7] 8.9 0.00 0.07 21746 compter_mots_ligne [7]
|
||||
0.04 0.01 250000/250000 est_mot_vide [8]
|
||||
0.00 0.01 150182/150182 compter_mot [11]
|
||||
-----------------------------------------------
|
||||
0.04 0.01 250000/250000 compter_mots_ligne [7]
|
||||
[8] 7.5 0.04 0.01 250000 est_mot_vide [8]
|
||||
0.01 0.00 12045671/12198772 egales [9]
|
||||
-----------------------------------------------
|
||||
0.00 0.00 153101/12198772 chercher_mot [10]
|
||||
0.01 0.00 12045671/12198772 est_mot_vide [8]
|
||||
[9] 2.1 0.01 0.00 12198772 egales [9]
|
||||
-----------------------------------------------
|
||||
0.01 0.00 150182/150182 compter_mot [11]
|
||||
[10] 1.4 0.01 0.00 150182 chercher_mot [10]
|
||||
0.00 0.00 153101/12198772 egales [9]
|
||||
0.00 0.00 150182/150182 hacher_mot [22]
|
||||
-----------------------------------------------
|
||||
0.00 0.01 150182/150182 compter_mots_ligne [7]
|
||||
[11] 1.4 0.00 0.01 150182 compter_mot [11]
|
||||
0.01 0.00 150182/150182 chercher_mot [10]
|
||||
0.00 0.00 5866/32667 copier [23]
|
||||
-----------------------------------------------
|
||||
0.01 0.00 1/1 main [1]
|
||||
[12] 1.4 0.01 0.00 1 decouper_lignes [12]
|
||||
-----------------------------------------------
|
||||
<spontaneous>
|
||||
[13] 1.4 0.01 0.00 _init [13]
|
||||
-----------------------------------------------
|
||||
0.00 0.00 5867/26801 construire_rapport [16]
|
||||
0.00 0.01 20934/26801 generer_vocabulaire [15]
|
||||
[14] 1.0 0.00 0.01 26801 ajouter [14]
|
||||
0.01 0.00 26801/1599863 longueur [2]
|
||||
0.00 0.00 26801/32667 copier [23]
|
||||
-----------------------------------------------
|
||||
0.00 0.01 1/1 main [1]
|
||||
[15] 0.8 0.00 0.01 1 generer_vocabulaire [15]
|
||||
0.00 0.01 20934/26801 ajouter [14]
|
||||
0.00 0.00 27934/928117 alea [21]
|
||||
-----------------------------------------------
|
||||
0.00 0.00 1/1 main [1]
|
||||
[16] 0.2 0.00 0.00 1 construire_rapport [16]
|
||||
0.00 0.00 5867/26801 ajouter [14]
|
||||
-----------------------------------------------
|
||||
0.00 0.00 1/1 main [1]
|
||||
[17] 0.2 0.00 0.00 1 ecart_type_longueur [17]
|
||||
0.00 0.00 5866/1599863 longueur [2]
|
||||
-----------------------------------------------
|
||||
0.00 0.00 1/1 main [1]
|
||||
[18] 0.2 0.00 0.00 1 longueur_moyenne [18]
|
||||
0.00 0.00 5866/1599863 longueur [2]
|
||||
-----------------------------------------------
|
||||
0.00 0.00 1539584/5297256 normaliser_ligne [3]
|
||||
0.00 0.00 3757672/5297256 analyser_bigrammes [24]
|
||||
[19] 0.0 0.00 0.00 5297256 est_lettre [19]
|
||||
-----------------------------------------------
|
||||
0.00 0.00 1266663/1266663 normaliser_ligne [3]
|
||||
[20] 0.0 0.00 0.00 1266663 en_minuscule [20]
|
||||
-----------------------------------------------
|
||||
0.00 0.00 27934/928117 generer_vocabulaire [15]
|
||||
0.00 0.00 900183/928117 generer_texte [26]
|
||||
[21] 0.0 0.00 0.00 928117 alea [21]
|
||||
-----------------------------------------------
|
||||
0.00 0.00 150182/150182 chercher_mot [10]
|
||||
[22] 0.0 0.00 0.00 150182 hacher_mot [22]
|
||||
-----------------------------------------------
|
||||
0.00 0.00 5866/32667 compter_mot [11]
|
||||
0.00 0.00 26801/32667 ajouter [14]
|
||||
[23] 0.0 0.00 0.00 32667 copier [23]
|
||||
-----------------------------------------------
|
||||
0.00 0.00 1/1 main [1]
|
||||
[24] 0.0 0.00 0.00 1 analyser_bigrammes [24]
|
||||
0.00 0.00 3757672/5297256 est_lettre [19]
|
||||
-----------------------------------------------
|
||||
0.00 0.00 1/1 main [1]
|
||||
[25] 0.0 0.00 0.00 1 empreinte [25]
|
||||
-----------------------------------------------
|
||||
0.00 0.00 1/1 main [1]
|
||||
[26] 0.0 0.00 0.00 1 generer_texte [26]
|
||||
0.00 0.00 900183/928117 alea [21]
|
||||
-----------------------------------------------
|
||||
0.00 0.00 1/1 main [1]
|
||||
[27] 0.0 0.00 0.00 1 racine [27]
|
||||
-----------------------------------------------
|
||||
|
||||
Index by function name
|
||||
|
||||
[13] _init [23] copier [26] generer_texte
|
||||
[14] ajouter [12] decouper_lignes [15] generer_vocabulaire
|
||||
[21] alea [5] doit_echanger [22] hacher_mot
|
||||
[24] analyser_bigrammes [17] ecart_type_longueur [2] longueur
|
||||
[10] chercher_mot [9] egales [18] longueur_moyenne
|
||||
[6] comparer [25] empreinte [3] normaliser_ligne
|
||||
[11] compter_mot [20] en_minuscule [27] racine
|
||||
[7] compter_mots_ligne [19] est_lettre [4] trier_entrees
|
||||
[16] construire_rapport [8] est_mot_vide
|
||||
Reference in New Issue
Block a user