diff --git a/COMPTE_RENDU.md b/COMPTE_RENDU.md new file mode 100644 index 0000000..a48c3f8 --- /dev/null +++ b/COMPTE_RENDU.md @@ -0,0 +1 @@ +// Piste : réduire le temps d'exécution de `doit_echanger` diff --git a/analyseur.c b/analyseur.c index 8fd5b0b..7081696 100644 --- a/analyseur.c +++ b/analyseur.c @@ -319,16 +319,33 @@ int doit_echanger(const Entree *a, const Entree *b) return comparer(a->mot, b->mot) > 0; } +/* La racine du tas est l'entrée qui doit apparaître le plus tard. */ +void retablir_tas(Entree *table, int n, int racine) +{ + while (racine < n / 2) { + int enfant = 2 * racine + 1; + if (enfant + 1 < n && doit_echanger(&table[enfant + 1], &table[enfant])) + enfant++; + if (!doit_echanger(&table[enfant], &table[racine])) + break; + + Entree tmp = table[racine]; + table[racine] = table[enfant]; + table[enfant] = tmp; + racine = enfant; + } +} + void trier_entrees(Entree *table, int n) { - for (int i = 0; i < n - 1; i++) { - for (int j = 0; j < n - 1 - i; j++) { - if (doit_echanger(&table[j], &table[j + 1])) { - Entree tmp = table[j]; - table[j] = table[j + 1]; - table[j + 1] = tmp; - } - } + /* Construire le tas, puis placer chaque racine à sa position finale. */ + for (int i = n / 2 - 1; i >= 0; i--) + retablir_tas(table, n, i); + for (int fin = n - 1; fin > 0; fin--) { + Entree tmp = table[0]; + table[0] = table[fin]; + table[fin] = tmp; + retablir_tas(table, fin, 0); } } diff --git a/notes.md b/notes.md index d34bfd2..663034c 100644 --- a/notes.md +++ b/notes.md @@ -365,4 +365,54 @@ Empreinte du rapport : 1d53de903efd2b7c real 0m0.751s user 0m0.703s sys 0m0.004s -``` \ No newline at end of file +``` + +## Remplacement du tri à bulles par un tri par tas + +Le tri à bulles effectuait systématiquement n(n - 1)/2 comparaisons, +soit 17 202 045 appels à `doit_echanger` pour 5 866 mots distincts. + +`trier_entrees` utilise maintenant un tri par tas. La racine du tas contient +l'entrée qui doit apparaître le plus tard dans le classement. Elle est +échangée avec la dernière entrée de la partie non triée, puis `retablir_tas` +restaure cette propriété sur les entrées restantes. La construction du tas +coûte O(n), puis les extractions coûtent O(log n) chacune : le tri complet +passe de O(n²) à O(n log n), y compris dans le pire cas. + +Le tri travaille directement dans le tableau, avec O(1) de mémoire +supplémentaire. `doit_echanger` reste inchangée : fréquence décroissante, +puis ordre alphabétique croissant. Chaque échange déplace l'entrée entière, +y compris la longueur mémorisée. + +Profil enregistré dans [out/profil_apres_tri.txt](out/profil_apres_tri.txt). +La comparaison porte sur la version précédente, décrite dans +`out/profil_apres_longueurs.txt`. + +| Mesure | Tri à bulles | Tri par tas | +| --- | ---: | ---: | +| Appels à `doit_echanger` pendant le tri | 17 202 045 | 129 602 | +| Appels à `comparer` pour départager les fréquences égales | 12 172 087 | 32 813 | +| Temps réel médian du programme sans profiling (`gcc -O0`, 5 exécutions par version) | 0,367 s | 0,113 s | + +Le tri effectue environ 133 fois moins de comparaisons d'entrées. +Le temps total baisse de 69,3 %, soit une accélération d'environ 3,25. +Les cinq mesures, réalisées en alternant les deux versions sur la même +machine, sont respectivement : 0,365 / 0,363 / 0,372 / 0,383 / 0,367 s +avant, et 0,112 / 0,113 / 0,115 / 0,113 / 0,112 s après. + +Validation : compilation sans avertissement avec `-Wall -Wextra -Wpedantic`, +rapport et sortie console identiques octet par octet sur les dix exécutions, +avec `-pg` et sous AddressSanitizer et UndefinedBehaviorSanitizer. +L'empreinte reste **`1d53de903efd2b7c`**. +Des tests temporaires ont comparé le résultat à `qsort` sur des tableaux +de 0 à 20 000 entrées, aléatoires, déjà triés ou inversés. Ils vérifient +aussi les fréquences égales, les doublons, les préfixes, les valeurs +extrêmes de fréquence et la conservation des longueurs des mots. + +Commandes pour reproduire ce profil : + +```bash +mkdir -p out/apres_tri +gcc -O0 -pg analyseur.c -o out/apres_tri/analyseur +(cd out/apres_tri && ./analyseur && gprof -b ./analyseur gmon.out > ../profil_apres_tri.txt) +``` diff --git a/out/analyseur b/out/analyseur index 65c902b..3c0d275 100755 Binary files a/out/analyseur and b/out/analyseur differ diff --git a/out/gmon.out b/out/gmon.out index ac86df0..7fe4e4c 100644 Binary files a/out/gmon.out and b/out/gmon.out differ diff --git a/out/profil_apres_tri.txt b/out/profil_apres_tri.txt new file mode 100644 index 0000000..dcfb79c --- /dev/null +++ b/out/profil_apres_tri.txt @@ -0,0 +1,160 @@ +Flat profile: + +Each sample counts as 0.01 seconds. + % cumulative self self total + time seconds seconds calls ms/call ms/call name + 45.45 0.05 0.05 12198772 0.00 0.00 egales + 18.18 0.07 0.02 1 20.00 27.09 analyser_bigrammes + 9.09 0.08 0.01 5297256 0.00 0.00 est_lettre + 9.09 0.09 0.01 1266663 0.00 0.00 en_minuscule + 9.09 0.10 0.01 250000 0.00 0.00 est_mot_vide + 9.09 0.11 0.01 1 10.00 10.00 decouper_lignes + 0.00 0.11 0.00 928117 0.00 0.00 alea + 0.00 0.11 0.00 150182 0.00 0.00 chercher_mot + 0.00 0.11 0.00 150182 0.00 0.00 compter_mot + 0.00 0.11 0.00 150182 0.00 0.00 hacher_mot + 0.00 0.11 0.00 129602 0.00 0.00 doit_echanger + 0.00 0.11 0.00 32813 0.00 0.00 comparer + 0.00 0.11 0.00 26801 0.00 0.00 ajouter + 0.00 0.11 0.00 21746 0.00 0.00 compter_mots_ligne + 0.00 0.11 0.00 21746 0.00 0.00 normaliser_ligne + 0.00 0.11 0.00 8798 0.00 0.00 retablir_tas + 0.00 0.11 0.00 5866 0.00 0.00 copier + 0.00 0.11 0.00 1 0.00 0.00 construire_rapport + 0.00 0.11 0.00 1 0.00 0.00 ecart_type_longueur + 0.00 0.11 0.00 1 0.00 0.00 empreinte + 0.00 0.11 0.00 1 0.00 0.00 generer_texte + 0.00 0.11 0.00 1 0.00 0.00 generer_vocabulaire + 0.00 0.11 0.00 1 0.00 0.00 longueur_moyenne + 0.00 0.11 0.00 1 0.00 0.00 racine + 0.00 0.11 0.00 1 0.00 0.00 trier_entrees + + Call graph + + +granularity: each sample hit covers 4 byte(s) for 9.09% of 0.11 seconds + +index % time self children called name + +[1] 100.0 0.00 0.11 main [1] + 0.00 0.06 21746/21746 compter_mots_ligne [2] + 0.02 0.01 1/1 analyser_bigrammes [5] + 0.00 0.01 21746/21746 normaliser_ligne [6] + 0.01 0.00 1/1 decouper_lignes [9] + 0.00 0.00 1/1 generer_vocabulaire [23] + 0.00 0.00 1/1 generer_texte [22] + 0.00 0.00 1/1 trier_entrees [26] + 0.00 0.00 1/1 longueur_moyenne [24] + 0.00 0.00 1/1 ecart_type_longueur [20] + 0.00 0.00 1/1 racine [25] + 0.00 0.00 1/1 construire_rapport [19] + 0.00 0.00 1/1 empreinte [21] +----------------------------------------------- + 0.00 0.06 21746/21746 main [1] +[2] 54.5 0.00 0.06 21746 compter_mots_ligne [2] + 0.01 0.05 250000/250000 est_mot_vide [3] + 0.00 0.00 150182/150182 compter_mot [11] +----------------------------------------------- + 0.01 0.05 250000/250000 compter_mots_ligne [2] +[3] 54.0 0.01 0.05 250000 est_mot_vide [3] + 0.05 0.00 12045671/12198772 egales [4] +----------------------------------------------- + 0.00 0.00 153101/12198772 chercher_mot [10] + 0.05 0.00 12045671/12198772 est_mot_vide [3] +[4] 45.5 0.05 0.00 12198772 egales [4] +----------------------------------------------- + 0.02 0.01 1/1 main [1] +[5] 24.6 0.02 0.01 1 analyser_bigrammes [5] + 0.01 0.00 3757672/5297256 est_lettre [7] +----------------------------------------------- + 0.00 0.01 21746/21746 main [1] +[6] 11.7 0.00 0.01 21746 normaliser_ligne [6] + 0.01 0.00 1266663/1266663 en_minuscule [8] + 0.00 0.00 1539584/5297256 est_lettre [7] +----------------------------------------------- + 0.00 0.00 1539584/5297256 normaliser_ligne [6] + 0.01 0.00 3757672/5297256 analyser_bigrammes [5] +[7] 9.1 0.01 0.00 5297256 est_lettre [7] +----------------------------------------------- + 0.01 0.00 1266663/1266663 normaliser_ligne [6] +[8] 9.1 0.01 0.00 1266663 en_minuscule [8] +----------------------------------------------- + 0.01 0.00 1/1 main [1] +[9] 9.1 0.01 0.00 1 decouper_lignes [9] +----------------------------------------------- + 0.00 0.00 150182/150182 compter_mot [11] +[10] 0.6 0.00 0.00 150182 chercher_mot [10] + 0.00 0.00 153101/12198772 egales [4] + 0.00 0.00 150182/150182 hacher_mot [13] +----------------------------------------------- + 0.00 0.00 150182/150182 compter_mots_ligne [2] +[11] 0.6 0.00 0.00 150182 compter_mot [11] + 0.00 0.00 150182/150182 chercher_mot [10] + 0.00 0.00 5866/5866 copier [18] +----------------------------------------------- + 0.00 0.00 27934/928117 generer_vocabulaire [23] + 0.00 0.00 900183/928117 generer_texte [22] +[12] 0.0 0.00 0.00 928117 alea [12] +----------------------------------------------- + 0.00 0.00 150182/150182 chercher_mot [10] +[13] 0.0 0.00 0.00 150182 hacher_mot [13] +----------------------------------------------- + 0.00 0.00 129602/129602 retablir_tas [17] +[14] 0.0 0.00 0.00 129602 doit_echanger [14] + 0.00 0.00 32813/32813 comparer [15] +----------------------------------------------- + 0.00 0.00 32813/32813 doit_echanger [14] +[15] 0.0 0.00 0.00 32813 comparer [15] +----------------------------------------------- + 0.00 0.00 5867/26801 construire_rapport [19] + 0.00 0.00 20934/26801 generer_vocabulaire [23] +[16] 0.0 0.00 0.00 26801 ajouter [16] +----------------------------------------------- + 0.00 0.00 8798/8798 trier_entrees [26] +[17] 0.0 0.00 0.00 8798 retablir_tas [17] + 0.00 0.00 129602/129602 doit_echanger [14] +----------------------------------------------- + 0.00 0.00 5866/5866 compter_mot [11] +[18] 0.0 0.00 0.00 5866 copier [18] +----------------------------------------------- + 0.00 0.00 1/1 main [1] +[19] 0.0 0.00 0.00 1 construire_rapport [19] + 0.00 0.00 5867/26801 ajouter [16] +----------------------------------------------- + 0.00 0.00 1/1 main [1] +[20] 0.0 0.00 0.00 1 ecart_type_longueur [20] +----------------------------------------------- + 0.00 0.00 1/1 main [1] +[21] 0.0 0.00 0.00 1 empreinte [21] +----------------------------------------------- + 0.00 0.00 1/1 main [1] +[22] 0.0 0.00 0.00 1 generer_texte [22] + 0.00 0.00 900183/928117 alea [12] +----------------------------------------------- + 0.00 0.00 1/1 main [1] +[23] 0.0 0.00 0.00 1 generer_vocabulaire [23] + 0.00 0.00 27934/928117 alea [12] + 0.00 0.00 20934/26801 ajouter [16] +----------------------------------------------- + 0.00 0.00 1/1 main [1] +[24] 0.0 0.00 0.00 1 longueur_moyenne [24] +----------------------------------------------- + 0.00 0.00 1/1 main [1] +[25] 0.0 0.00 0.00 1 racine [25] +----------------------------------------------- + 0.00 0.00 1/1 main [1] +[26] 0.0 0.00 0.00 1 trier_entrees [26] + 0.00 0.00 8798/8798 retablir_tas [17] +----------------------------------------------- + +Index by function name + + [16] ajouter [9] decouper_lignes [23] generer_vocabulaire + [12] alea [14] doit_echanger [13] hacher_mot + [5] analyser_bigrammes [20] ecart_type_longueur [24] longueur_moyenne + [10] chercher_mot [4] egales [6] normaliser_ligne + [15] comparer [21] empreinte [25] racine + [11] compter_mot [8] en_minuscule [17] retablir_tas + [2] compter_mots_ligne [7] est_lettre [26] trier_entrees + [19] construire_rapport [3] est_mot_vide + [18] copier [22] generer_texte