forked from menault/2026_TD1_DEV51_Qualite_Algo
Optimise la fonction est_mot_vide en remplaçant la recherche linéaire par un index de hachage, réduisant ainsi le temps de recherche et améliorant les performances globales.
This commit is contained in:
+44
-10
@@ -110,6 +110,7 @@ static const char *MOTS_VIDES[] = {
|
|||||||
"ses", "leur", "leurs", "mon", "ma", "mes", "ne", "pas", "plus", "est"
|
"ses", "leur", "leurs", "mon", "ma", "mes", "ne", "pas", "plus", "est"
|
||||||
};
|
};
|
||||||
#define NB_MOTS_VIDES ((int)(sizeof(MOTS_VIDES) / sizeof(MOTS_VIDES[0])))
|
#define NB_MOTS_VIDES ((int)(sizeof(MOTS_VIDES) / sizeof(MOTS_VIDES[0])))
|
||||||
|
#define TAILLE_HACHAGE_VIDES (2 * NB_MOTS_VIDES + 1)
|
||||||
|
|
||||||
static const char *SYLLABES[] = {
|
static const char *SYLLABES[] = {
|
||||||
"ba", "be", "bi", "bo", "ca", "co", "da", "de", "di", "fa", "fi", "ga",
|
"ba", "be", "bi", "bo", "ca", "co", "da", "de", "di", "fa", "fi", "ga",
|
||||||
@@ -227,15 +228,6 @@ typedef struct {
|
|||||||
int taille;
|
int taille;
|
||||||
} Entree;
|
} Entree;
|
||||||
|
|
||||||
int est_mot_vide(const char *mot)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < NB_MOTS_VIDES; i++) {
|
|
||||||
if (egales(mot, MOTS_VIDES[i]))
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned int hacher_mot(const char *mot)
|
unsigned int hacher_mot(const char *mot)
|
||||||
{
|
{
|
||||||
unsigned int h = 2166136261u;
|
unsigned int h = 2166136261u;
|
||||||
@@ -246,6 +238,48 @@ unsigned int hacher_mot(const char *mot)
|
|||||||
return h;
|
return h;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Construire un index séparé sans modifier la liste utilisée par le générateur.
|
||||||
|
* L'index est initialement rempli de zéros ; chaque case contient indice + 1. */
|
||||||
|
static int initialiser_mots_vides(int *index_mots_vides)
|
||||||
|
{
|
||||||
|
int taille_max = 0;
|
||||||
|
for (int i = 0; i < NB_MOTS_VIDES; i++) {
|
||||||
|
int taille = 0;
|
||||||
|
while (MOTS_VIDES[i][taille] != '\0')
|
||||||
|
taille++;
|
||||||
|
if (taille > taille_max)
|
||||||
|
taille_max = taille;
|
||||||
|
|
||||||
|
int position = hacher_mot(MOTS_VIDES[i]) % TAILLE_HACHAGE_VIDES;
|
||||||
|
while (index_mots_vides[position] != 0 &&
|
||||||
|
!egales(MOTS_VIDES[index_mots_vides[position] - 1], MOTS_VIDES[i]))
|
||||||
|
position = (position + 1) % TAILLE_HACHAGE_VIDES;
|
||||||
|
/* Un doublon, comme "ou", réutilise la même case. */
|
||||||
|
index_mots_vides[position] = i + 1;
|
||||||
|
}
|
||||||
|
return taille_max;
|
||||||
|
}
|
||||||
|
|
||||||
|
int est_mot_vide(const char *mot, int taille)
|
||||||
|
{
|
||||||
|
static int index_mots_vides[TAILLE_HACHAGE_VIDES];
|
||||||
|
static int taille_max = -1;
|
||||||
|
if (taille_max < 0)
|
||||||
|
taille_max = initialiser_mots_vides(index_mots_vides);
|
||||||
|
|
||||||
|
/* La taille provient du découpage : aucun nouveau parcours du mot. */
|
||||||
|
if (taille > taille_max)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
int position = hacher_mot(mot) % TAILLE_HACHAGE_VIDES;
|
||||||
|
while (index_mots_vides[position] != 0) {
|
||||||
|
if (egales(mot, MOTS_VIDES[index_mots_vides[position] - 1]))
|
||||||
|
return 1;
|
||||||
|
position = (position + 1) % TAILLE_HACHAGE_VIDES;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* L'index contient indice + 1 ; zéro désigne une case libre.
|
/* 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. */
|
* 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 chercher_mot(const Entree *table, const int *index_mots, const char *mot)
|
||||||
@@ -302,7 +336,7 @@ void compter_mots_ligne(Entree *table, int *nb_entrees, int *index_mots,
|
|||||||
mot[k] = '\0';
|
mot[k] = '\0';
|
||||||
if (k > 0) {
|
if (k > 0) {
|
||||||
(*nb_mots_total)++;
|
(*nb_mots_total)++;
|
||||||
if (!est_mot_vide(mot))
|
if (!est_mot_vide(mot, k))
|
||||||
compter_mot(table, nb_entrees, index_mots, mot, k);
|
compter_mot(table, nb_entrees, index_mots, mot, k);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -511,3 +511,93 @@ mkdir -p out/apres_bigrammes
|
|||||||
gcc -O0 -pg analyseur.c -o out/apres_bigrammes/analyseur
|
gcc -O0 -pg analyseur.c -o out/apres_bigrammes/analyseur
|
||||||
(cd out/apres_bigrammes && ./analyseur && gprof -b ./analyseur gmon.out > ../profil_apres_bigrammes.txt)
|
(cd out/apres_bigrammes && ./analyseur && gprof -b ./analyseur gmon.out > ../profil_apres_bigrammes.txt)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Optimisation de `est_mot_vide`
|
||||||
|
|
||||||
|
La recherche linéaire dans les 60 entrées de `MOTS_VIDES` est remplacée
|
||||||
|
par un index de hachage de 121 cases, construit au premier appel seulement.
|
||||||
|
Chaque case contient l'indice d'un mot + 1, ou zéro si elle est libre.
|
||||||
|
Les collisions sont résolues en avançant à la case suivante, avec retour
|
||||||
|
au début. Le doublon `ou` réutilise la même case : l'index contient donc
|
||||||
|
59 mots distincts et reste rempli à moins de 50 %.
|
||||||
|
|
||||||
|
L'initialisation calcule aussi la longueur maximale des mots vides,
|
||||||
|
actuellement 5 caractères. `est_mot_vide` reçoit la longueur `k`, déjà
|
||||||
|
connue lors du découpage, et rejette immédiatement les mots plus longs.
|
||||||
|
Sur ce texte, 107 019 mots sont ainsi exclus avant même le hachage.
|
||||||
|
Les autres recherches sont en O(1) en moyenne pour ces mots de longueur
|
||||||
|
bornée, avec un pire cas linéaire en cas de nombreuses collisions.
|
||||||
|
|
||||||
|
L'index est séparé de `MOTS_VIDES` : la liste originale, son ordre et son
|
||||||
|
doublon sont conservés pour produire exactement le même texte.
|
||||||
|
L'index et la longueur maximale occupent 488 octets sur cette machine,
|
||||||
|
sans allocation dynamique supplémentaire.
|
||||||
|
|
||||||
|
Profil enregistré dans [out/profil_apres_mots_vides.txt](out/profil_apres_mots_vides.txt).
|
||||||
|
La comparaison porte sur la version précédente, décrite dans
|
||||||
|
`out/profil_apres_bigrammes.txt`.
|
||||||
|
|
||||||
|
| Mesure | Avant | Après |
|
||||||
|
| --- | ---: | ---: |
|
||||||
|
| Appels à `est_mot_vide` | 250 000 | 250 000 |
|
||||||
|
| Appels à `egales` depuis `est_mot_vide` | 12 045 671 | 202 444 |
|
||||||
|
| Appels à `egales` lors de l'initialisation de l'index | 0 | 28 |
|
||||||
|
| Appels à `egales` dans tout le programme | 12 198 772 | 355 573 |
|
||||||
|
| Temps réel médian sans profiling (`gcc -O0`, 10 exécutions par version) | 0,1007 s | 0,0543 s |
|
||||||
|
|
||||||
|
La recherche des mots vides effectue environ 60 fois moins de comparaisons.
|
||||||
|
Le temps total baisse de 46,1 %, soit une accélération d'environ 1,86.
|
||||||
|
Les mesures ont été réalisées en alternant les deux versions sur la même
|
||||||
|
machine :
|
||||||
|
|
||||||
|
- Avant : 0,0998 / 0,0953 / 0,0997 / 0,1015 / 0,1170 / 0,1067 / 0,1102 /
|
||||||
|
0,0999 / 0,1018 / 0,0989 s.
|
||||||
|
- Après : 0,0531 / 0,0535 / 0,0565 / 0,0542 / 0,0554 / 0,0544 / 0,0577 /
|
||||||
|
0,0579 / 0,0520 / 0,0522 s.
|
||||||
|
|
||||||
|
Validation : compilation sans avertissement avec `-Wall -Wextra -Wpedantic`,
|
||||||
|
rapport et sortie console identiques octet par octet sur les vingt
|
||||||
|
exécutions, avec `-pg` et sous AddressSanitizer et UndefinedBehaviorSanitizer.
|
||||||
|
L'empreinte reste **`1d53de903efd2b7c`**.
|
||||||
|
Un test temporaire a comparé les réponses à une recherche linéaire pour
|
||||||
|
les 60 entrées, leurs préfixes et suffixes, les 18 278 chaînes minuscules
|
||||||
|
de 1 à 3 caractères, 50 000 mots pseudo-aléatoires et les 7 000 mots du
|
||||||
|
vocabulaire. Un dictionnaire synthétique dans un processus séparé a
|
||||||
|
également vérifié les collisions avec retour à la première case et
|
||||||
|
l'adaptation automatique à des mots vides de plus de 5 caractères.
|
||||||
|
|
||||||
|
Commandes pour reproduire ce profil :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p out/apres_mots_vides
|
||||||
|
gcc -O0 -pg analyseur.c -o out/apres_mots_vides/analyseur
|
||||||
|
(cd out/apres_mots_vides && ./analyseur && gprof -b ./analyseur gmon.out > ../profil_apres_mots_vides.txt)
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
[rabot@salle222-12 out]$ gcc -O0 ../analyseur.c -o analyseur
|
||||||
|
[rabot@salle222-12 out]$ time ./analyseur
|
||||||
|
Lignes analysées : 21746
|
||||||
|
Mots au total : 250000
|
||||||
|
Mots distincts : 5866
|
||||||
|
Longueur moyenne : 6.712 (écart-type 1.873)
|
||||||
|
Bigramme le + courant : "me" (17607 fois, souvent suivi de 'n')
|
||||||
|
|
||||||
|
Top 10 :
|
||||||
|
1. rane 287
|
||||||
|
2. fasi 241
|
||||||
|
3. tata 219
|
||||||
|
4. mopo 214
|
||||||
|
5. cofi 180
|
||||||
|
6. vesi 179
|
||||||
|
7. laganomo 177
|
||||||
|
8. boto 172
|
||||||
|
9. nopebo 169
|
||||||
|
10. rotion 165
|
||||||
|
|
||||||
|
Empreinte du rapport : 1d53de903efd2b7c
|
||||||
|
|
||||||
|
real 0m0.078s
|
||||||
|
user 0m0.059s
|
||||||
|
sys 0m0.001s
|
||||||
|
```
|
||||||
Binary file not shown.
Reference in New Issue
Block a user