From e3076e46aacc93faed65c8360810c9472541a042 Mon Sep 17 00:00:00 2001 From: rabot Date: Wed, 7 Oct 2026 13:49:42 +0200 Subject: [PATCH] =?UTF-8?q?Optimise=20la=20fonction=20d'analyse=20des=20bi?= =?UTF-8?q?grammes=20en=20r=C3=A9duisant=20les=20v=C3=A9rifications=20et?= =?UTF-8?q?=20en=20normalisant=20les=20lignes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- analyseur.c | 18 ++++++++++--- notes.md | 70 +++++++++++++++++++++++++++++++++++++++++++++++++- out/analyseur | Bin 27328 -> 26800 bytes 3 files changed, 83 insertions(+), 5 deletions(-) diff --git a/analyseur.c b/analyseur.c index 7081696..c4e1f23 100644 --- a/analyseur.c +++ b/analyseur.c @@ -373,7 +373,8 @@ double ecart_type_longueur(const Entree *table, int n, double moyenne) } /* Recherche du bigramme de lettres le plus fréquent, avec en bonus la - * troisième lettre qui le suit le plus souvent. */ + * troisième lettre qui le suit le plus souvent. + * Les lignes doivent être normalisées : lettres 'a' à 'z' ou espaces. */ void analyser_bigrammes(char **lignes, int nb_lignes, char *meilleur, int *occ_meilleur) { @@ -381,9 +382,18 @@ void analyser_bigrammes(char **lignes, int nb_lignes, for (int l = 0; l < nb_lignes; l++) { const char *s = lignes[l]; - for (int i = 0; s[i] != '\0' && s[i + 1] != '\0' && s[i + 2] != '\0'; i++) { - if (est_lettre(s[i]) && est_lettre(s[i + 1]) && est_lettre(s[i + 2])) - trigrammes[s[i] - 'a'][s[i + 1] - 'a'][s[i + 2] - 'a']++; + int avant_dernier = -1; + int dernier = -1; + for (int i = 0; s[i] != '\0'; i++) { + if (s[i] == ' ') { + avant_dernier = dernier = -1; + continue; + } + int lettre = s[i] - 'a'; + if (avant_dernier >= 0) + trigrammes[avant_dernier][dernier][lettre]++; + avant_dernier = dernier; + dernier = lettre; } } diff --git a/notes.md b/notes.md index bf224f7..3160449 100644 --- a/notes.md +++ b/notes.md @@ -442,4 +442,72 @@ Empreinte du rapport : 1d53de903efd2b7c real 0m0.354s user 0m0.314s sys 0m0.008s -``` \ No newline at end of file +``` + +## Réduction des vérifications dans `analyser_bigrammes` + +L'ancienne boucle examinait des fenêtres de trois caractères et appelait +`est_lettre` jusqu'à trois fois par fenêtre. Les caractères des fenêtres +voisines étaient donc vérifiés plusieurs fois, pour un total de +3 757 672 appels dans cette fonction. + +Les lignes sont déjà normalisées avant l'analyse : elles ne contiennent +que des lettres de `a` à `z` et des espaces. La nouvelle boucle utilise +cette propriété, explicitée dans le commentaire de la fonction, et +conserve les indices des deux lettres précédentes. Chaque nouvelle lettre +complète un trigramme si deux lettres la précèdent. Un espace ou une +nouvelle ligne réinitialise cet historique. + +Les trigrammes qui se chevauchent sont conservés, sans compter à travers +les séparateurs. Le calcul du meilleur bigramme et les règles de départage +restent identiques. Les deux versions sont en O(nombre de caractères) : +le gain vient des vérifications et des appels évités, sans tableau +supplémentaire. + +Profil enregistré dans [out/profil_apres_bigrammes.txt](out/profil_apres_bigrammes.txt). +La comparaison porte sur la version précédente, décrite dans +`out/profil_apres_tri.txt`. + +| Mesure | Avant | Après | +| --- | ---: | ---: | +| Appels à `est_lettre` depuis `analyser_bigrammes` | 3 757 672 | 0 | +| Appels à `est_lettre` dans tout le programme | 5 297 256 | 1 539 584 | +| Temps réel médian sans profiling (`gcc -O0`, 10 exécutions par version) | 0,1252 s | 0,1123 s | + +Le gain médian mesuré est de 10,3 %. Les exécutions sont courtes et les +temps fluctuent ; cette valeur est indicative. Les mesures ont été faites +en alternant les deux versions sur la même machine : + +- Avant : 0,1339 / 0,1132 / 0,1220 / 0,1146 / 0,1403 / 0,1118 / 0,1268 / + 0,1235 / 0,1430 / 0,1373 s. +- Après : 0,1332 / 0,1035 / 0,1077 / 0,1133 / 0,1178 / 0,1131 / 0,1095 / + 0,0994 / 0,1114 / 0,1193 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`**. Le bigramme reste `me`, avec +17 607 occurrences et `n` comme troisième lettre la plus fréquente. +Un test temporaire a comparé 43 cas à l'ancienne fonction : entrées vides +ou courtes, chevauchements, espaces, changements de ligne, égalités, +normalisation et données pseudo-aléatoires. + +Autres pistes, à mesurer séparément : + +- `est_mot_vide` provoque encore 12 045 671 appels à `egales`. Réutiliser + la longueur `k` pour exclure les mots plus longs que tous les mots vides, + puis consulter un index de hachage séparé, reste la priorité. L'ordre + du tableau `MOTS_VIDES`, utilisé pour générer le texte, doit être conservé. +- `normaliser_ligne` pourrait utiliser une table de conversion des octets + en minuscules ou espaces, pour regrouper classification et conversion. +- `decouper_lignes` pourrait utiliser des pointeurs dans le texte et + remplacer les sauts de ligne par `\0`, évitant les allocations et copies + par ligne. La libération de la mémoire devrait être adaptée en conséquence. + +Commandes pour reproduire ce profil : + +```bash +mkdir -p out/apres_bigrammes +gcc -O0 -pg analyseur.c -o out/apres_bigrammes/analyseur +(cd out/apres_bigrammes && ./analyseur && gprof -b ./analyseur gmon.out > ../profil_apres_bigrammes.txt) +``` diff --git a/out/analyseur b/out/analyseur index 3c0d27544abf3d772994107472f6a5f7ad6528b7..e030422cc0658c93fa1ad56c1b92578b2e07ae85 100755 GIT binary patch delta 6656 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 delta 8072 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 E0J_*kJOBUy