From 13318119ae97c3b2a76b99fdf85ce82d908865de Mon Sep 17 00:00:00 2001 From: rabot Date: Wed, 7 Oct 2026 13:59:32 +0200 Subject: [PATCH] =?UTF-8?q?Optimise=20la=20fonction=20est=5Fmot=5Fvide=20e?= =?UTF-8?q?n=20rempla=C3=A7ant=20la=20recherche=20lin=C3=A9aire=20par=20un?= =?UTF-8?q?=20index=20de=20hachage,=20r=C3=A9duisant=20ainsi=20le=20temps?= =?UTF-8?q?=20de=20recherche=20et=20am=C3=A9liorant=20les=20performances?= =?UTF-8?q?=20globales.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- analyseur.c | 54 ++++++++++++++++++++++++------ notes.md | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++ out/analyseur | Bin 26800 -> 26928 bytes 3 files changed, 134 insertions(+), 10 deletions(-) diff --git a/analyseur.c b/analyseur.c index c4e1f23..9194077 100644 --- a/analyseur.c +++ b/analyseur.c @@ -110,6 +110,7 @@ static const char *MOTS_VIDES[] = { "ses", "leur", "leurs", "mon", "ma", "mes", "ne", "pas", "plus", "est" }; #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[] = { "ba", "be", "bi", "bo", "ca", "co", "da", "de", "di", "fa", "fi", "ga", @@ -227,15 +228,6 @@ typedef struct { int taille; } 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 h = 2166136261u; @@ -246,6 +238,48 @@ unsigned int hacher_mot(const char *mot) 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. * 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) @@ -302,7 +336,7 @@ void compter_mots_ligne(Entree *table, int *nb_entrees, int *index_mots, mot[k] = '\0'; if (k > 0) { (*nb_mots_total)++; - if (!est_mot_vide(mot)) + if (!est_mot_vide(mot, k)) compter_mot(table, nb_entrees, index_mots, mot, k); } } diff --git a/notes.md b/notes.md index 3160449..45e24be 100644 --- a/notes.md +++ b/notes.md @@ -511,3 +511,93 @@ 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) ``` + +## 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 +``` \ No newline at end of file diff --git a/out/analyseur b/out/analyseur index e030422cc0658c93fa1ad56c1b92578b2e07ae85..45fcfa2763e7136e4210cfd3b3c1eac4c73d6057 100755 GIT binary patch delta 3725 zcmdmRk#WN%#t9ls1&I^2?r}YpV*rDva+8G^^*C>EfO!lzCVMhkFiK5sVr&zv@PLTH z>B*8z>Vi9WLvIayvivR=ZYv-ZVt*AE~Qj=O@*ed)%?z;N94 z2T15OOS9__#%|Xy9y2_8MUH}mJbGE(O&J(Gx<#0}U4MAY@L;^azm1{gK~j2VW^%{MC8z;7@tYQ=OrIxjD0jr2qf_W3@B%OY!X_vT~U-Fm$spHNRl22l*2ouf@h7(?ys( zKw$^*YtmPcUrX3`FfuSOy!IzMHhNj7nSm{BJ;2|Z`~UxcuzyZYF3^@`V*5L}U%P@S z=gVYHotql(L4n=Nn;^u%uuH)Y6e8z5I*)^uzi9sW|NnlFOJ3~!IGIb=p8fPEP{96} ze9=s7@_rrG$=SL+j3*}l&}~(6_2_n;;nB_D(Hp=BW_Aj6ho151b-e)b*umeEXXz>E zFaHZN-~=drb-ONrrDgtBmH+?$Pw?n={gL9K&7!Koz`#(#`oi`73N;levt0CL5Y&Fa}SaWopbQKlz;LIwpY^ zlk3bhM2>NQ!nif#|Ns9UogkmRIQeSwF0*W=SdPi+=CVv2&nLT?%Q4Bmnw(~C#5CpE zf%HNYE%4|S)d8D;kSYS} zY4PY4)koD6`S}0;Py7O|H$1vsFL?Bd8nQAlr15K2I*&mtWCdB+%Nh^1?D&ggk0yIqhw*blxDX{1A5C6st)}&o1r%hU0t6x) z`3UTv2cSUWg|NYa$M|USUu!kBn-4+3Gy@V$Git7Sbi3{VDLwcQtZf5`z4hT_Pa8F+ z-iMP5ZN#NZ9zt`KM|UVF*@~z_lWq6I&5Lbx88>QE76d zt1_eCaU6mQLCLeTFuD|x=|Nn51!H#?aZA{L*Y)nBs>>LaXGN5F`$iTo0 z3KCErfyp!HGlCR=e$YPZaaNfV6|n_h4d3X3%`` z|9=#U0ikdMGME?`l3)J+uZJS93779+VqhqIh1~-R3=9k_m>3vVyqdhsO^)ph69Yr{ zo5`o#3$$aioYzE8>42AC|>$yv@1u!!({QWT5&s~bG zfSG~8>eJ*rcPX|B%nS@IpCl;{Hia{X-#A9H10#(n+2vRS_ppXU;UmygL5MyLu zU}5<9A0mn%6~O8R7~Z%uOgMg}p42eAk>AeQ-L#xQY7FR*F}h8OW5F$7MS zY#1iaSO_9z`xqG*gcx`jEKMQuJrIgv5hDYG!em8eVa6$wCx(ejf{I*_xCA0+4%}KzWOSfdN#KfYhllF)#=*@iN@71Mv`8 z3*=75tzgo1^22a(#sDxo6+}vwgLEgo%Mckb#eZ!2zNSmS#Rf zO_`$$5eH{T5OeauNO419W{{ht7!FiGQVd7}ge90EAzN(>;xRCUL8WI*PK*@yRD;T1 zQiaI=gvOHzRQxD36|8}ZyF-Jwog`!688jURZxsh1=a8j3=H7%g@J(qR3wAM4{1S+fravNsA=!@A>yFC z0AlR|xlQs6RMrrhKA;iHaC7p;C~?MDAT^A?L8L4n3j>276J~0YVu8k!qzMZoObabQ z<})zFLc`R3vSYNkWD-c0iHm^&RCqxYfXR%>h0!m~GV?M^G81z$i&Kl@bMs4zA37Vdj`lLc9YH$O<-$jBoC u^%2N8km%$M=?T0W*g;$d22kZKIaxMCoAW|6M5KChV1_x9cf#bZ3^f3H)^jZY delta 3022 zcmdmRiE+b4#t9ls9}*^N-Q(IS!vF?bWhM(T>Tz!10P`3&O!j27V3e5L#MmYn-~kbX z(~~8c)CDI%Md9@1Kqhr8YCDHG+%^Q-^8{&X5aPa z7D<+dzWJP6c{WvSUc+23%j6@zSyyouXJGRi36IX&7sp-yfb_pM>vsL)F~g%*9-ZerI*)^Oyjb`7 z|Ns3U!(Kf3JlRy=p5@z@|Nkdn)RUV$O}~fn@ML9!R#^v+Zr2$e-3%VR0gPZ~r+`PV z>jjW|)c#C9WuTzH=I{Ui-L5A*k}r64yDsoxyuiPWq2&O7tLFdz|0j5KyZ%V=&}LE9 zU|?VPqQ>)I`U%jDa!_?UtE)YtR$Eoa7|9LQfJ!FHM!r)MDO)$kP)sc zJi1+1?3-|bnE~vrw_w2y9=)Lr9^I}RprOz4Ve&^St$OE=|Nl3;_Aqw4Hdwm$)Lh-q zV9LYrV)t{fo&)<>7#JA#JI-NXc+mk7I^oe=dw`*Y-J`ko1VgElM>p$vEl`Zuc5r|q zHq(q_yt^Vcyzm7@aPpyWM^PVi{2cF@R$+~uGYMf6%!88LBOfzb(dUU() z04e?W7_4mri2d~OC!<-~a!=hna!l;rsvp?=Ukk z!0_P9|Nk*83=F|v|NpOgD&!n36meX$TRMp%;+l5C^=ctRi061vZJd!qt@g^ zS7pYq$*r#PjQW$8x+*gkO+M+WtaR_m|Njx7^2(7Z=iKD#*Dx_KG{5@)KMYxyYcn&m4_G7E>I+N^3|n9S|DS>4v}BOM zVEGSB3=Dr?|Nq~EBHs>|7hq;!$b9qvKgccyxXYF^@vw8$gAFiXW?=aG?*IQ-WJfSZ zfL#D`dH^#6gVBfo|FcjmNQGNaz|6o<^Wp#hVifs2xcmfW28Qz={{Pnjl|XO@sI%~} zPk!aDq-1mBKNtuwFfed2FfhRQ4;UC2L>U+ucy4ai^H|I}d4aITL;= z26C+7O{i>>3Pe)?RQ4@MoQaoVg$+a;Mt%nAVypu>Q<9Gf;?WSOUQnd~QYQ(LWz++c z4wDNb#2G!n?C8l0Bg73s$sOd{w@~HbAT10G3}sN$I4mFn;55U)z)%a;J8?2&q`2e) zCI$vUCP{`4w7^?C*)UREau3vmLT!i%KOq#uNstNkcbFI$1Q?_k4wON?2$g*Z)#Ysn z5ddd61_p*isJMV4L|hF*F}#P0Us8n#{Del%U#R$0XtaYf6$1kUD3b{>@CY$1G=gXV z=T-&=2F1yVk>ZS|lNF=H>m8XPK{?MHq6VCy7#JA5q2krhKmlhI1_lOD#sdYSg)&5a zEreo-gsRWdfe1uF{hA6Dmx2ZjEYLw26zsSox)2TELW+Tbp&9JBsgoZ@iAyemYBqu< zy9p4b44c7fj(|wn>&%dtVSwf`s1k<9%nS?)lNFhTW&c7=aWsR-fy)$7_^>cAD1lTk zYENDmEzW2-d1v&?%{ODNv25Ou_<)INy7c5pDSBM6gt)?H@~M)OMaM$SS-!A#Ecg5421Gn+6h{Y><(_o52p| gGsrS9Fo;c_o1x9w5e*S>nS3$Boas#bWUfp#0DCB76aWAK