Profil après optimisation et compte rendu du TD3

profil_apres.txt : profil gprof de la version finale (0,05 s profilées
contre 38,10 s). COMPTE_RENDU.md : complexités avant/après de F1 à F10,
M de chaque fonction calculé à la main (maximum 14 → 5), profils avant
et après, justification de chaque optimisation et résultats du concours
(3,05 s → 0,061 s en -O2, empreinte inchangée).

Groupe : Ryad MEHIRI, Luka PLOUVIER, Jimmy COLOMB-RAVAT.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-21 11:05:42 +02:00
parent 9ee6245859
commit 931e0ade75
2 changed files with 586 additions and 0 deletions
+413
View File
@@ -0,0 +1,413 @@
# Compte rendu — TD3 : complexité algorithmique
**R5.A.04 · Qualité Algorithmique**
**Groupe :** Ryad MEHIRI · Luka PLOUVIER · Jimmy COLOMB-RAVAT
Programme étudié : [`banque.c`](banque.c). Il génère 30 000 transactions pseudo-aléatoires (graine fixe), calcule une dizaine d'indicateurs (fonctions F1 à F10) et écrit `rapport.txt`. L'empreinte du rapport doit rester `ff68d260a7a58441`.
Tailles : **n** = 30 000 transactions, **C** = 2 000 clients, **J** = 1 000 jours, **Q** = 30 000 requêtes, **S** = 400 centimes à rendre, **k** = 8 pièces, **L** = 120 348 caractères de rapport (6 017 lignes).
---
## Résumé
| | Avant | Après |
|---|:---:|:---:|
| Temps, `gcc -O2` (médiane de 5 exécutions) | **3,05 s** | **0,061 s** (×50) |
| `./evaluer.sh` (médiane de 5, en ms) | 2 945 ms (2ᵉ essai : 3 855) | **124 ms** (124 à 142 sur 3 essais) |
| Empreinte | `ff68d260a7a58441` | `ff68d260a7a58441` ✔ |
| M maximal du fichier (hors `alea` et `generer_transactions`) | 14 (`categorie`) | **5** |
| Durée de l'exécution profilée (`-O0 -pg`) | 38,10 s | 0,05 s |
| Objectifs de complexité atteints | 0 / 10 | **10 / 10** |
Il y a 11 commits d'optimisation : un par fonction F1 à F10, plus un pour découper `main`. À chaque étape, l'empreinte, `rapport.txt` (octet par octet) et la sortie console restent identiques à ceux de la version d'origine.
---
## 1. Environnement de mesure
| | |
|---|---|
| Machine | Intel Core i7-1165G7 (4 cœurs / 8 threads), 7,8 Go de RAM, portable |
| Système | Windows 11, environnement **MSYS** de MSYS2 |
| Compilateur | gcc 15.3.0 (`-O2` pour les temps, `-O0 -g -pg` pour les profils) |
| Profiler | GNU gprof 2.47 |
Nous avons utilisé l'environnement MSYS, où `long` fait 64 bits comme sous Linux. En Windows natif (MinGW), `unsigned long` ne fait que 32 bits et `alea()` (`etat_alea >> 33`) ne renvoie que des zéros. Nous l'avons constaté lors du TD1 : les données générées sont alors fausses.
Sur ce portable, le **bruit de mesure** est important : d'une exécution à l'autre, on observe ±0,2 s sur la version de 1 s, et la version d'origine a donné 2,9 s puis 3,9 s avec `evaluer.sh`. Nous retenons donc des médianes. Quand un gain était plus petit que ce bruit, nous l'avons vérifié par des exécutions alternées des deux versions (partie 5, F10).
```bash
gcc -O2 banque.c -o banque && time ./banque # temps (5 fois)
gcc -O0 -g -pg banque.c -o banque_pg && ./banque_pg
gprof -b ./banque_pg gmon.out > profil_avant.txt # profil
```
---
## 2. Mesure initiale
| Exécution | 1 | 2 | 3 | 4 | 5 | **Médiane** |
|---|:---:|:---:|:---:|:---:|:---:|:---:|
| Temps réel, `-O2` (s) | 4,14 | 3,02 | 2,77 | 3,05 | 3,57 | **3,05** |
Le cours annonçait environ 2,7 s sur la machine de l'enseignant.
```
Clients distincts : 2000
Somme des requêtes : -2624578
Meilleure période : jours 650 à 986 (3548830)
Paires opposées : 5832
Façons de rendre 400 : 1960497
Empreinte : ff68d260a7a58441
```
---
## 3. Profil initial ([`profil_avant.txt`](profil_avant.txt))
```
% cumulative self self total
time seconds seconds calls s/call s/call name
37.93 14.45 14.45 _mcount_private
24.41 23.75 9.30 1 9.30 9.30 trier_par_montant
9.17 27.25 3.50 1 3.50 3.50 nb_facons_rendre
9.00 30.68 3.43 6017 0.00 0.00 longueur
5.77 32.88 2.20 30000 0.00 0.00 rechercher_par_id
5.25 34.88 2.00 __fentry__
4.72 36.67 1.80 1 1.80 1.80 nb_paires_opposees
2.10 37.48 0.80 1 0.80 0.80 meilleure_periode
0.58 37.70 0.22 1 0.22 0.22 nb_clients_distincts
0.58 37.91 0.22 1 0.22 0.22 total_par_client
0.26 38.02 0.10 1 0.10 0.10 total_par_jour
0.14 38.07 0.06 30000 0.00 0.00 categorie
```
Le graphe d'appels indique **1 + 266 723 738** appels pour `nb_facons_rendre` : c'est la récursion. `_mcount_private` et `__fentry__` (16,45 s) sont le code ajouté par `-pg` à **chaque appel de fonction**. Presque tous ces appels viennent de F8, donc le vrai coût de F8 dans ce profil est d'environ 3,5 + 16,5 = **20 s**. C'est l'**effet de sonde** : il a disparu dès que F8 n'a plus été récursive (partie 5).
### Les fonctions les plus coûteuses ont-elles les pires complexités ?
| Rang | Fonction | Complexité | Opérations pour nos tailles | Temps propre (profil) |
|:---:|---|---|---|:---:|
| 1 | F1 `trier_par_montant` | O(n²) | n(n−1)/2 = **450 millions** de comparaisons, avec échanges | 9,30 s |
| 2 | F8 `nb_facons_rendre` | exponentielle (≈ S^k) | **266,7 millions** d'appels | 3,50 s (+ ~16 s de sonde) |
| 3 | F10 `ajouter_ligne` → `longueur` | O(L) par ligne, O(L²) au total | **341 millions** de caractères reparcourus | 3,43 s |
| 4 | F6 `rechercher_par_id` | O(n) par requête, O(Q × n) | ≈ Q × n/2 = **450 millions** de comparaisons | 2,20 s |
| 5 | F7 `nb_paires_opposees` | O(n²) | **450 millions** d'additions | 1,80 s |
| 6 | F5 `meilleure_periode` | O(J³) | J(J+1)(J+2)/6 = **167 millions** d'additions | 0,80 s |
| 7 | F2 `nb_clients_distincts` | O(n²) au pire | ≈ n × C ici (le client est retrouvé tôt) | 0,22 s |
| 8 | F3 `total_par_client` | O(n × C) | **60 millions** de tests | 0,22 s |
| 9 | F4 `total_par_jour` | O(n × J) | **30 millions** de tests | 0,10 s |
| 10 | F9 `categorie` | O(1) par transaction | 30 000 appels | 0,06 s |
**Oui, dans l'ensemble.** Les cinq fonctions les plus coûteuses sont exactement celles dont la complexité est quadratique ou pire. Mais le **degré** de la complexité ne suffit pas à les classer, il faut aussi tenir compte des tailles réelles et du coût de chaque opération. On le voit sur deux cas :
- **F5 a la pire complexité polynomiale (cubique), mais n'est que 6ᵉ.** J = 1 000 est petit : J³/6 = 167 millions d'opérations, moins que les n²/2 = 450 millions de F1 ou F7.
- **F1 et F7 font le même nombre de tours (450 millions), mais F1 est 5 fois plus lente.** Chaque tour de F1 compare deux critères et échange des structures de 24 octets, alors que F7 fait une addition et un test. C'est la **constante** que la notation O ignore.
---
## 4. Complexités et complexité cyclomatique, avant / après
### 4.1 Complexité de F1 à F10
| F | Fonction | Avant | Objectif | Après | Comment |
|:---:|---|---|---|---|---|
| F1 | `trier_par_montant` | O(n²) | O(n log n) | **O(n log n)** ✔ | `qsort` |
| F2 | `nb_clients_distincts` | O(n²) au pire | O(n + C) | **O(n + C)** ✔ | tableau `vu[C]` |
| F3 | `total_par_client` | O(n × C) | O(n + C) | **O(n + C)** ✔ | un seul parcours |
| F4 | `total_par_jour` | O(n × J) | O(n + J) | **O(n + J)** ✔ | un seul parcours |
| F5 | `meilleure_periode` | O(J³) | O(J) | **O(J)** ✔ | sommes préfixes |
| F6 | `rechercher_par_id` | O(n) par requête | O(1) par requête | **O(1)** par requête ✔ (index construit une fois en O(n)) | tableau `position[id]` |
| F7 | `nb_paires_opposees` | O(n²) | O(n log n) | **O(n log n)** ✔ | n dichotomies sur le tableau trié par F1 |
| F8 | `nb_facons_rendre` | exponentielle | O(S × k) | **O(S × k)** ✔ | programmation dynamique |
| F9 | `categorie` | O(1), M = 14 | M ≤ 10 | **O(1), M ≤ 5** ✔ | découpage en 5 fonctions |
| F10 | `ajouter_ligne` | O(L) par ligne, O(L²) au total | O(L) au total | **O(L) au total** ✔ | on passe la position de fin |
### 4.2 Complexité cyclomatique M, calculée à la main
Nous avons compté **M = décisions + 1**, avec +1 pour chaque `if`, `else if`, `for`, `while`, `&&`, `||` et ternaire `?:`. Comme le demande `evaluer.sh`, `alea` et `generer_transactions` ne comptent pas dans le maximum.
**Avant**
| Fonction | Décisions | M |
|---|---|:---:|
| `trier_par_montant` (F1) | `for`, `for`, `if`, `\|\|`, `&&` | 6 |
| `nb_clients_distincts` (F2) | `for`, `for`, `if`, `if` | 5 |
| `total_par_client` (F3) | `for`, `for`, `if` | 4 |
| `total_par_jour` (F4) | `for`, `for`, `if` | 4 |
| `meilleure_periode` (F5) | `for`, `for`, `for`, `if` | 5 |
| `rechercher_par_id` (F6) | `for`, `if` | 3 |
| `nb_paires_opposees` (F7) | `for`, `for`, `if` | 4 |
| `nb_facons_rendre` (F8) | `if`, `if`, `\|\|` | 4 |
| `categorie` (F9) | `if` (< 0), `if` (< −20 000), `if` + `\|\|` (week-end), `else if` (< −5 000), `if` (client), `else if` (== 0), `if` (> 30 000), `if` + `&&`, `else if` (> 10 000), `else if` (> 1 000), `if` (jour % 30) : 13 | **14** |
| `longueur` (F10) | `while` | 2 |
| `ajouter_ligne` (F10) | `while` | 2 |
| `empreinte` | `for` | 2 |
| `main` | 6 `for`, 2 `if` | 9 |
| *`alea`, `generer_transactions` (exclues)* | *—, puis `for`, `if`, `&&`, `for`* | *1, 5* |
**Maximum : M = 14** (`categorie`).
**Après**
| Fonction | Décisions | M |
|---|---|:---:|
| `comparer_transactions` (F1) | `if` | 2 |
| `trier_par_montant` (F1) | — | 1 |
| `nb_clients_distincts` (F2) | `for`, `if` | 3 |
| `total_par_client` (F3) | `for`, `for` | 3 |
| `total_par_jour` (F4) | `for`, `for` | 3 |
| `meilleure_periode` (F5) | `for`, `if`, `if` | 4 |
| `indexer_par_id` (F6) | `for` | 2 |
| `rechercher_par_id` (F6) | — | 1 |
| `premier_indice` (F7) | `while`, `if` | 3 |
| `effectif` (F7) | — | 1 |
| `nb_paires_opposees` (F7) | `for` | 2 |
| `nb_facons_rendre` (F8) | `for`, `for` | 3 |
| `est_week_end` (F9) | — | 1 |
| `categorie_debit` (F9) | `if`, `?:`, `if`, `?:` | **5** |
| `categorie_gros_credit` (F9) | `&&`, `?:` | 3 |
| `categorie_credit` (F9) | `if`, `if`, `if`, `?:` | **5** |
| `categorie` (F9) | `if`, `if` | 3 |
| `ajouter_ligne` (F10) | `while` | 2 |
| `empreinte` | `for` | 2 |
| `sommer_requetes` | `for` | 2 |
| `compter_categories` | `for` | 2 |
| `analyser` | — | 1 |
| `ajouter_formatee` | — | 1 |
| `construire_rapport` | 4 `for` | **5** |
| `ecrire_fichier` | `if` | 2 |
| `afficher` | — | 1 |
| `main` | — | 1 |
**Maximum : M = 5**, atteint par `categorie_debit`, `categorie_credit` et `construire_rapport`.
On pourrait descendre plus bas avec des astuces comme `return 2 - est_week_end(jour);` à la place du ternaire, ou en coupant le rapport en morceaux. Nous ne l'avons pas fait : chaque fonction à M = 5 se lit d'un coup d'œil, et `construire_rapport` garde tout le format du rapport au même endroit.
---
## 5. Optimisations réalisées (un commit par optimisation)
L'ordre suit le profil : on traite d'abord la fonction la plus coûteuse. Seule exception, F7 utilise le tableau trié par F1 et vient donc après lui. Après chaque commit, nous avons vérifié l'empreinte, `rapport.txt` (identique octet par octet) et la sortie console.
### F1 — `cb7857c` : `qsort` au lieu du tri à bulles
```c
int comparer_transactions(const void *pa, const void *pb)
{
const Transaction *a = pa, *b = pb;
if (a->montant != b->montant)
return (a->montant > b->montant) - (a->montant < b->montant);
return (a->id > b->id) - (a->id < b->id);
}
```
**Pourquoi c'est correct :** les id sont tous distincts, donc l'ordre (montant, puis id) est **total**. `qsort` n'est pas stable, mais il donne exactement le même classement que le tri à bulles. L'écriture `(a > b) - (a < b)` évite tout débordement, contrairement à une soustraction.
**O(n²) → O(n log n). Temps : 3,05 s → 1,17 s.**
### F8 — `d19085e` : programmation dynamique
```c
long *facons = calloc(somme + 1, sizeof(long));
facons[0] = 1;
for (int p = 0; p < nb_pieces; p++)
for (int s = PIECES[p]; s <= somme; s++)
facons[s] += facons[s - PIECES[p]];
```
**Pourquoi c'est plus rapide :** la récursion recalculait sans cesse les mêmes couples (somme, pièces) : 266,7 millions d'appels. Après avoir traité les p premières pièces, `facons[s]` est le nombre de façons de rendre s avec ces pièces. On compte des **combinaisons**, sans tenir compte de l'ordre des pièces, comme la récursion d'origine. On fait ainsi 2 820 additions au lieu de 266 millions d'appels.
**Exponentielle → O(S × k). Temps : 1,17 s → 1,00 s.** En `-O2`, la récursion ne coûtait donc qu'environ 0,2 s, un écart proche du bruit de mesure. Dans le profil, en revanche, les 16 s de `_mcount_private` disparaissent.
### F10 — `4babca1` : rapport en O(L)
```c
char *ajouter_ligne(char *fin, const char *ligne)
{
while (*ligne != '\0')
*fin++ = *ligne++;
*fin = '\0';
return fin;
}
```
**Pourquoi c'est plus rapide :** `longueur(rapport)` reparcourait tout le rapport à chaque ligne, soit 341 millions de caractères pour 6 017 lignes. On passe maintenant la position du `'\0'` final et on renvoie la nouvelle. Chaque caractère n'est écrit qu'une fois. `longueur` n'est plus utilisée et disparaît.
**O(L²) → O(L). Temps : 0,90 s → 0,72 s**, mesuré par 11 exécutions alternées des deux versions. La mesure habituelle de 5 exécutions (1,00 → 1,08 s) était noyée dans le bruit. En `-O2`, gcc intègre `longueur` dans `ajouter_ligne`, mais la boucle reste octet par octet (nous l'avons vérifié dans l'assembleur) : le coût quadratique était donc bien réel.
### F6 — `d913c22` : index par id
```c
int *indexer_par_id(const Transaction *t, int n)
{
int *position = malloc(n * sizeof(int));
for (int i = 0; i < n; i++)
position[t[i].id] = i;
return position;
}
```
**Pourquoi c'est correct :** `generer_transactions` attribue les id 0 à n−1, puis les mélange. Les id forment donc une **permutation** et chaque id a sa case. `rechercher_par_id` devient `return &t[position[id]];`. Le test `r != NULL` de `main` ne pouvait plus jamais être vrai et a été retiré. L'index est construit avant le tri de F1, car les requêtes portent sur l'ordre d'origine.
**O(n) → O(1) par requête (index en O(n) une seule fois). Temps : 1,08 s → 0,92 s.**
### F7 — `c84ff59` : paires opposées par dichotomie
```c
long nb_paires_opposees(const Transaction *t, int n) /* t trié par F1 */
{
long nb = 0;
for (int i = 0; i < n; i++)
nb += effectif(t, n, -t[i].montant);
return (nb - effectif(t, n, 0)) / 2;
}
```
`effectif(t, n, v)` vaut `premier_indice(v + 1) − premier_indice(v)`, où `premier_indice` est une recherche dichotomique en O(log n).
**Pourquoi c'est correct :** la somme compte, pour chaque transaction, les transactions de montant opposé. Chaque paire {a, b} est donc comptée deux fois (une fois depuis a, une fois depuis b), et une transaction de montant 0 se compte aussi elle-même. On retire ces auto-comptages, puis on divise par 2. Le résultat reste bien 5 832 paires. F7 réutilise le tri de F1 et est donc appelée après lui dans `main`.
**O(n²) → O(n log n) (n recherches en O(log n), le tri étant déjà fait pour F1). Temps : 0,92 s → 0,25 s.**
### F5 — `9227039` : sommes préfixes, un seul parcours
```c
for (int j = 0; j < nb_jours; j++) {
if (prefixe < prefixe_min) { /* prefixe vaut ici prefixe(j) */
prefixe_min = prefixe;
debut_min = j;
}
prefixe += totaux[j]; /* prefixe vaut maintenant prefixe(j+1) */
if (prefixe - prefixe_min > meilleur) {
meilleur = prefixe - prefixe_min;
*debut = debut_min;
*fin = j;
}
}
```
**Pourquoi c'est correct :** la somme des jours i à j vaut `prefixe(j+1) − prefixe(i)`. Pour une fin j fixée, le meilleur début est donc l'indice du plus petit préfixe avant j. L'original gardait la **première** période maximale dans l'ordre (i, puis j). Pour la retrouver, on garde le **premier** minimum (`<` strict) et on ne remplace le meilleur que s'il est **strictement** dépassé. On peut montrer que cela donne le même couple (début, fin), même en cas d'égalité. Nous l'avons vérifié sur 200 000 tableaux aléatoires remplis d'égalités, en comparant à la triple boucle d'origine.
**O(J³) → O(J). Temps : 0,25 s → 0,097 s.**
### F3 — `b402946` et F4 — `6c8b915` : un seul parcours
```c
for (int c = 0; c < NB_CLIENTS; c++)
totaux[c] = 0;
for (int i = 0; i < n; i++)
totaux[t[i].client] += t[i].montant;
```
Au lieu de reparcourir les n transactions pour chaque client (ou chaque jour), chaque montant va directement dans la bonne case. Les montants d'un même client sont additionnés dans le même ordre, et ce sont des entiers, donc les totaux sont identiques. F4 est identique avec `jour`.
**F3 : O(n × C) → O(n + C), 0,097 s → 0,087 s. F4 : O(n × J) → O(n + J), 0,087 s → 0,073 s.**
### F2 — `3bf75a2` : tableau de présence
```c
char vu[NB_CLIENTS] = {0};
for (int i = 0; i < n; i++)
if (!vu[t[i].client]) {
vu[t[i].client] = 1;
nb++;
}
```
**O(n²) au pire → O(n + C). Temps : 0,073 s → 0,069 s**, au niveau du bruit. Dans le profil `-O0`, la fonction passe de 0,32 s à 0.
### F9 — `60fd5e5` : `categorie` découpée (M = 14 → 5)
```c
int categorie(const Transaction *t)
{
if (t->montant < 0)
return categorie_debit(t);
if (t->montant == 0)
return 0;
return categorie_credit(t);
}
```
`categorie_debit` et `categorie_credit` (M = 5), `categorie_gros_credit` (M = 3) et le prédicat `est_week_end` (`jour % 7 >= 5`, équivalent à `== 5 || == 6` puisque `jour ≥ 0`) remplacent les `if` imbriqués.
**Pourquoi c'est correct :** nous avons comparé l'ancienne et la nouvelle fonction sur **tout le domaine**. Le montant va de −50 000 à 50 000. Pour le jour, seuls `jour % 7` et `jour % 30` comptent, donc 210 valeurs suffisent (ppcm(7, 30) = 210). Pour le client, seul `client % 10` compte. Les 210 002 100 combinaisons donnent la même catégorie.
**Complexité inchangée (O(1)), M maximal 14 → 5.**
### `main` — `9ee6245` : fonctions courtes (M = 8 → 1)
Après F9, `main` (6 boucles et 1 test) avait le plus grand M du fichier. Les calculs passent dans `analyser`, qui remplit une structure `Resultats`. Le rapport est construit par `construire_rapport`, à l'aide de `ajouter_formatee` (un `vsnprintf` suivi d'un `ajouter_ligne`). `ecrire_fichier` et `afficher` terminent. **Temps : 0,068 s → 0,061 s, rapport identique octet par octet.**
### Évolution du temps
| Étape | Commit | Temps `-O2` (médiane de 5) | Durée profilée (`-O0 -pg`) |
|---|---|:---:|:---:|
| Version d'origine | — | 3,05 s | 38,10 s |
| F1 `qsort` | `cb7857c` | 1,17 s | 30,17 s |
| F8 programmation dynamique | `d19085e` | 1,00 s | 9,12 s |
| F10 rapport en O(L) | `4babca1` | 1,08 s* | 6,34 s |
| F6 index par id | `d913c22` | 0,92 s | 4,62 s |
| F7 dichotomie | `c84ff59` | 0,25 s | 1,90 s |
| F5 sommes préfixes | `9227039` | 0,097 s | 0,62 s |
| F3 un seul parcours | `b402946` | 0,087 s | 0,48 s |
| F4 un seul parcours | `6c8b915` | 0,073 s | 0,38 s |
| F2 tableau de présence | `3bf75a2` | 0,069 s | 0,03 s |
| F9 découpage (M) | `60fd5e5` | 0,068 s | 0,04 s |
| `main` découpée (M) | `9ee6245` | 0,061 s | 0,05 s |
\* Bruit de mesure. Avec 11 exécutions alternées des deux versions, F10 fait passer de 0,90 s à 0,72 s.
---
## 6. Validation ([`profil_apres.txt`](profil_apres.txt))
```
% cumulative self self total
time seconds seconds calls ms/call ms/call name
40.00 0.02 0.02 __fentry__
40.00 0.04 0.02 _mcount_private
20.00 0.05 0.01 149998 0.00 0.00 alea
0.00 0.05 0.00 60002 0.00 0.00 premier_indice
0.00 0.05 0.00 30001 0.00 0.00 effectif
0.00 0.05 0.00 30000 0.00 0.00 categorie
0.00 0.05 0.00 30000 0.00 0.00 rechercher_par_id
```
- **Il ne reste plus que 5 échantillons** (0,05 s). Le seul qui tombe dans le programme lui-même est dans `alea`, c'est-à-dire la génération des données, qu'on n'a pas le droit de modifier.
- Les nombres d'appels correspondent aux nouvelles complexités. `premier_indice` est appelée 60 002 fois : 2 fois par transaction pour F7, plus 2 pour les montants nuls. `rechercher_par_id` est appelée 30 000 fois, une par requête, mais chaque appel est en O(1).
- `comparer_transactions` n'a **aucun nombre d'appels** : elle est appelée par `qsort`, qui est dans la bibliothèque C et n'est pas instrumentée. C'est la limite de gprof vue en cours (« gprof ne voit pas la libc »).
**Ce qui reste à gagner :** nous avons mesuré le plancher (médiane de 11 exécutions) :
| Programme | Temps |
|---|:---:|
| `int main(void) { return 0; }` (démarrage d'un processus) | 32 ms |
| Génération des transactions seule (non modifiable) | 41 ms |
| Version finale complète | **45 ms** |
Nos dix fonctions ne coûtent plus qu'environ **4 ms** sur 45. Une optimisation supplémentaire, par exemple un tri par dénombrement ou un parcours à deux indices pour F7, ne serait pas mesurable face au bruit. Nous nous sommes donc arrêtés là.
---
## 7. Concours
| Critère | Résultat |
|---|---|
| Complexité algorithmique | les 10 objectifs sont atteints (partie 4.1) |
| Complexité cyclomatique maximale | **M = 5**, calculée à la main (partie 4.2). Lizard n'est pas installé, donc `evaluer.sh` affiche « - ». |
| Temps (`./evaluer.sh banque.c MEHIRI_PLOUVIER_COLOMB-RAVAT`) | **124 ms**, empreinte correcte. Les deux autres essais ont donné 142 et 124 ms. |
La ligne `MEHIRI_PLOUVIER_COLOMB-RAVAT;-;124` a été ajoutée à `resultats.csv`. Ce temps a été mesuré sur notre portable sous Windows. Chaque exécution y paie environ 30 ms de démarrage de processus, et `evaluer.sh` lance aussi `date` deux fois par mesure. Il n'est donc pas comparable à une mesure sur la machine de l'enseignant.
---
## 8. Bilan
- **« Le bon algorithme bat la machine » :** de 3,05 s à 0,061 s (×50), sans changer la machine ni les options de compilation, uniquement en changeant les algorithmes.
- **« La théorie prédit, la mesure confirme » :** les fonctions quadratiques ou pires étaient bien les plus coûteuses. Mais leur ordre dépendait des tailles réelles (F5 cubique, mais J petit) et des constantes (F1 cinq fois plus lente que F7 pour le même nombre de tours).
- **Coût des optimisations :** l'index de F6 occupe 120 Ko (30 000 entiers). F7 suppose le tableau trié par F1, ce qui est indiqué en commentaire et dans `main`. F8 alloue S + 1 entiers. Pour la complexité cyclomatique, le fichier a plus de fonctions (27 au lieu de 13), mais chacune est courte, avec M ≤ 5 au lieu de 14 au maximum.
- **Limites de gprof rencontrées :** l'effet de sonde (16 s de `_mcount` dues aux 267 millions d'appels récursifs de F8) et `qsort`, qui reste invisible.
+173
View File
@@ -0,0 +1,173 @@
Flat profile:
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls ms/call ms/call name
40.00 0.02 0.02 __fentry__
40.00 0.04 0.02 _mcount_private
20.00 0.05 0.01 149998 0.00 0.00 alea
0.00 0.05 0.00 60002 0.00 0.00 premier_indice
0.00 0.05 0.00 30001 0.00 0.00 effectif
0.00 0.05 0.00 30000 0.00 0.00 categorie
0.00 0.05 0.00 30000 0.00 0.00 rechercher_par_id
0.00 0.05 0.00 15066 0.00 0.00 categorie_debit
0.00 0.05 0.00 14934 0.00 0.00 categorie_credit
0.00 0.05 0.00 9677 0.00 0.00 est_week_end
0.00 0.05 0.00 6017 0.00 0.00 ajouter_formatee
0.00 0.05 0.00 6017 0.00 0.00 ajouter_ligne
0.00 0.05 0.00 5957 0.00 0.00 categorie_gros_credit
0.00 0.05 0.00 1 0.00 0.00 afficher
0.00 0.05 0.00 1 0.00 0.00 analyser
0.00 0.05 0.00 1 0.00 0.00 compter_categories
0.00 0.05 0.00 1 0.00 0.00 construire_rapport
0.00 0.05 0.00 1 0.00 0.00 ecrire_fichier
0.00 0.05 0.00 1 0.00 0.00 empreinte
0.00 0.05 0.00 1 0.00 10.00 generer_transactions
0.00 0.05 0.00 1 0.00 0.00 indexer_par_id
0.00 0.05 0.00 1 0.00 0.00 meilleure_periode
0.00 0.05 0.00 1 0.00 0.00 nb_clients_distincts
0.00 0.05 0.00 1 0.00 0.00 nb_facons_rendre
0.00 0.05 0.00 1 0.00 0.00 nb_paires_opposees
0.00 0.05 0.00 1 0.00 0.00 sommer_requetes
0.00 0.05 0.00 1 0.00 0.00 total_par_client
0.00 0.05 0.00 1 0.00 0.00 total_par_jour
0.00 0.05 0.00 1 0.00 0.00 trier_par_montant
Call graph
granularity: each sample hit covers 4 byte(s) for 20.00% of 0.05 seconds
index % time self children called name
<spontaneous>
[1] 40.0 0.02 0.00 __fentry__ [1]
-----------------------------------------------
<spontaneous>
[2] 40.0 0.02 0.00 _mcount_private [2]
-----------------------------------------------
0.01 0.00 149998/149998 generer_transactions [4]
[3] 20.0 0.01 0.00 149998 alea [3]
-----------------------------------------------
0.00 0.01 1/1 main [5]
[4] 20.0 0.00 0.01 1 generer_transactions [4]
0.01 0.00 149998/149998 alea [3]
-----------------------------------------------
<spontaneous>
[5] 20.0 0.00 0.01 main [5]
0.00 0.01 1/1 generer_transactions [4]
0.00 0.00 1/1 analyser [17]
0.00 0.00 1/1 construire_rapport [19]
0.00 0.00 1/1 ecrire_fichier [20]
0.00 0.00 1/1 afficher [16]
-----------------------------------------------
0.00 0.00 60002/60002 effectif [7]
[6] 0.0 0.00 0.00 60002 premier_indice [6]
-----------------------------------------------
0.00 0.00 30001/30001 nb_paires_opposees [26]
[7] 0.0 0.00 0.00 30001 effectif [7]
0.00 0.00 60002/60002 premier_indice [6]
-----------------------------------------------
0.00 0.00 30000/30000 compter_categories [18]
[8] 0.0 0.00 0.00 30000 categorie [8]
0.00 0.00 15066/15066 categorie_debit [10]
0.00 0.00 14934/14934 categorie_credit [11]
-----------------------------------------------
0.00 0.00 30000/30000 sommer_requetes [27]
[9] 0.0 0.00 0.00 30000 rechercher_par_id [9]
-----------------------------------------------
0.00 0.00 15066/15066 categorie [8]
[10] 0.0 0.00 0.00 15066 categorie_debit [10]
0.00 0.00 9042/9677 est_week_end [12]
-----------------------------------------------
0.00 0.00 14934/14934 categorie [8]
[11] 0.0 0.00 0.00 14934 categorie_credit [11]
0.00 0.00 5957/5957 categorie_gros_credit [15]
-----------------------------------------------
0.00 0.00 635/9677 categorie_gros_credit [15]
0.00 0.00 9042/9677 categorie_debit [10]
[12] 0.0 0.00 0.00 9677 est_week_end [12]
-----------------------------------------------
0.00 0.00 6017/6017 construire_rapport [19]
[13] 0.0 0.00 0.00 6017 ajouter_formatee [13]
0.00 0.00 6017/6017 ajouter_ligne [14]
-----------------------------------------------
0.00 0.00 6017/6017 ajouter_formatee [13]
[14] 0.0 0.00 0.00 6017 ajouter_ligne [14]
-----------------------------------------------
0.00 0.00 5957/5957 categorie_credit [11]
[15] 0.0 0.00 0.00 5957 categorie_gros_credit [15]
0.00 0.00 635/9677 est_week_end [12]
-----------------------------------------------
0.00 0.00 1/1 main [5]
[16] 0.0 0.00 0.00 1 afficher [16]
0.00 0.00 1/1 empreinte [21]
-----------------------------------------------
0.00 0.00 1/1 main [5]
[17] 0.0 0.00 0.00 1 analyser [17]
0.00 0.00 1/1 sommer_requetes [27]
0.00 0.00 1/1 nb_clients_distincts [24]
0.00 0.00 1/1 total_par_client [28]
0.00 0.00 1/1 total_par_jour [29]
0.00 0.00 1/1 meilleure_periode [23]
0.00 0.00 1/1 compter_categories [18]
0.00 0.00 1/1 trier_par_montant [30]
0.00 0.00 1/1 nb_paires_opposees [26]
0.00 0.00 1/1 nb_facons_rendre [25]
-----------------------------------------------
0.00 0.00 1/1 analyser [17]
[18] 0.0 0.00 0.00 1 compter_categories [18]
0.00 0.00 30000/30000 categorie [8]
-----------------------------------------------
0.00 0.00 1/1 main [5]
[19] 0.0 0.00 0.00 1 construire_rapport [19]
0.00 0.00 6017/6017 ajouter_formatee [13]
-----------------------------------------------
0.00 0.00 1/1 main [5]
[20] 0.0 0.00 0.00 1 ecrire_fichier [20]
-----------------------------------------------
0.00 0.00 1/1 afficher [16]
[21] 0.0 0.00 0.00 1 empreinte [21]
-----------------------------------------------
0.00 0.00 1/1 sommer_requetes [27]
[22] 0.0 0.00 0.00 1 indexer_par_id [22]
-----------------------------------------------
0.00 0.00 1/1 analyser [17]
[23] 0.0 0.00 0.00 1 meilleure_periode [23]
-----------------------------------------------
0.00 0.00 1/1 analyser [17]
[24] 0.0 0.00 0.00 1 nb_clients_distincts [24]
-----------------------------------------------
0.00 0.00 1/1 analyser [17]
[25] 0.0 0.00 0.00 1 nb_facons_rendre [25]
-----------------------------------------------
0.00 0.00 1/1 analyser [17]
[26] 0.0 0.00 0.00 1 nb_paires_opposees [26]
0.00 0.00 30001/30001 effectif [7]
-----------------------------------------------
0.00 0.00 1/1 analyser [17]
[27] 0.0 0.00 0.00 1 sommer_requetes [27]
0.00 0.00 30000/30000 rechercher_par_id [9]
0.00 0.00 1/1 indexer_par_id [22]
-----------------------------------------------
0.00 0.00 1/1 analyser [17]
[28] 0.0 0.00 0.00 1 total_par_client [28]
-----------------------------------------------
0.00 0.00 1/1 analyser [17]
[29] 0.0 0.00 0.00 1 total_par_jour [29]
-----------------------------------------------
0.00 0.00 1/1 analyser [17]
[30] 0.0 0.00 0.00 1 trier_par_montant [30]
-----------------------------------------------
Index by function name
[1] __fentry__ [15] categorie_gros_credit [24] nb_clients_distincts
[2] _mcount_private [18] compter_categories [25] nb_facons_rendre
[16] afficher [19] construire_rapport [26] nb_paires_opposees
[13] ajouter_formatee [20] ecrire_fichier [6] premier_indice
[14] ajouter_ligne [7] effectif [9] rechercher_par_id
[3] alea [21] empreinte [27] sommer_requetes
[17] analyser [12] est_week_end [28] total_par_client
[8] categorie [4] generer_transactions [29] total_par_jour
[11] categorie_credit [22] indexer_par_id [30] trier_par_montant
[10] categorie_debit [23] meilleure_periode