Numérique et Sciences Informatiques1ère

Mesurer les performances
Exercices corrigés

Maîtrisez la mesure des performances : complexité algorithmique, benchmarking, optimisation et analyse comparative grâce à ces 5 exercices détaillés.

Concepts & Exercices
Performance = \frac{Résultats}{Temps_{exécution} + Espace_{mémoire}}
Calcul de la performance
Complexité temporelle
⏱️ O(n)
Temps d'exécution en fonction de la taille
Complexité spatiale
💾 O(n)
Usage mémoire en fonction de la taille
Benchmarking
📈 Comparaison
Mesure comparative des performances
Indicateurs de performance :
  • Temps d'exécution : Durée pour compléter une tâche
  • Utilisation mémoire : Quantité de RAM utilisée
  • Complexité algorithmique : O(n), O(n²), O(log n), etc.
  • Utilisation CPU : Charge processeur pendant l'exécution
Exercice 1
Analyse de complexité d'un algorithme de recherche
Exercice 2
Benchmarking de deux algorithmes de tri
Exercice 3
Analyse de l'utilisation mémoire
Exercice 4
Optimisation d'une fonction récursive
Exercice 5
Comparaison de performances entre implémentations
Corrigé : Exercices 1 à 3
1 Analyse complexité recherche
Définition :

Problème : Analyser la complexité d'un algorithme de recherche linéaire

Méthode d'analyse :
  1. Identifier les opérations élémentaires
  2. Compter le nombre d'opérations en fonction de la taille
  3. Déterminer la complexité dans le pire des cas
  4. Comparer avec la complexité théorique
1
Compter les opérations
2
Identifier le pire cas
3
Exprimer en notation O
4
Valider la complexité
Étape 1 : Algorithme de recherche linéaire
FONCTION RechercheLineaire(tableau, element) : Entier
  POUR i DE 0 A LONGUEUR(tableau) - 1 FAIRE
    SI tableau[i] = element ALORS
      RETOURNER i
    FIN SI
  FIN POUR
  RETOURNER -1
FIN
Étape 2 : Analyse des opérations

Opérations dans la boucle :
• Comparaison tableau[i] = element : 1 opération
• Incrémentation de i : 1 opération
• Comparaison de i avec la taille : 1 opération
Dans le pire cas : L'élément n'est pas dans le tableau, la boucle s'exécute n fois

Étape 3 : Calcul de la complexité

Nombre d'opérations : 3n (dans le pire cas)
Complexité temporelle : O(n)
Complexité spatiale : O(1) - pas de mémoire supplémentaire
Conclusion : Algorithme linéaire en temps, constant en espace

Complexité : O(n) temporel, O(1) spatial
Analyse complète :

L'algorithme de recherche linéaire a une complexité linéaire dans le pire des cas, ce qui est acceptable pour des petites tailles mais inefficace pour des grandes données.

Règles d'analyse de complexité :

Compter les opérations dominantes : Boucles, comparaisons

Identifier le pire cas : Situation où l'algorithme met le plus de temps

Simplifier l'expression : Garder le terme dominant

2 Benchmarking algorithmes tri
Définition :

Problème : Comparer les performances de deux algorithmes de tri

1
Implémenter les deux algorithmes
2
Créer des jeux de test
3
Mesurer les temps d'exécution
4
Comparer et analyser
Étape 1 : Algorithmes à comparer
FONCTION TriInsertion(tableau) : Tableau
  POUR i DE 1 A LONGUEUR(tableau) - 1 FAIRE
    cle ← tableau[i]
    j ← i - 1
    TANT QUE j ≥ 0 ET tableau[j] > cle FAIRE
      tableau[j + 1] ← tableau[j]
      j ← j - 1
    FIN TANT QUE
    tableau[j + 1] ← cle
  FIN POUR
  RETOURNER tableau
FIN

FONCTION TriRapide(tableau) : Tableau
  SI LONGUEUR(tableau) ≤ 1 ALORS
    RETOURNER tableau
  FIN SI
  
  pivot ← tableau[LONGUEUR(tableau) DIV 2]
  gauche ← [x POUR x IN tableau SI x < pivot]
  milieu ← [x POUR x IN tableau SI x = pivot]
  droite ← [x POUR x IN tableau SI x > pivot]
  
  RETOURNER TriRapide(gauche) + milieu + TriRapide(droite)
FIN
Étape 2 : Résultats du benchmarking
Taille TriInsertion TriRapide Avantage
100 0.002s 0.001s TriRapide
1,000 0.150s 0.008s TriRapide
10,000 14.5s 0.09s TriRapide
100,000 ~24min 1.2s TriRapide
Étape 3 : Analyse comparative

TriInsertion : O(n²) - Performances dégradées avec la taille
TriRapide : O(n log n) en moyenne - Beaucoup plus efficace
Point de bascule : Vers 1000 éléments, TriRapide devient nettement plus rapide
Conclusion : Pour de grandes données, TriRapide est préférable

TriRapide plus performant à partir de 1000 éléments
Conclusion du benchmark :

Malgré sa complexité théorique supérieure dans le pire des cas, le TriRapide est beaucoup plus efficace en pratique pour des données de taille moyenne à grande.

Règles de benchmarking :

Utiliser des jeux de test réalistes : Données représentatives

Mesurer plusieurs fois : Prendre la moyenne pour lisser les variations

Comparer avec la complexité théorique : Vérifier la cohérence

3 Analyse utilisation mémoire
Définition :

Problème : Analyser l'utilisation mémoire d'une fonction récursive

Espace_{mémoire} = Variables_{locales} + Profondeur_{récursion} × Taille_{appel}
Espace_{factorielle} = O(n) \text{ à cause de la pile d'appels}
Étape 1 : Fonction récursive - Calcul de factorielle
FONCTION FactorielleRec(n) : Entier
  SI n = 0 OU n = 1 ALORS
    RETOURNER 1
  SINON
    RETOURNER n * FactorielleRec(n - 1)
  FIN SI
FIN
Étape 2 : Analyse de la consommation mémoire

Variables locales par appel : 1 variable entière (n)
Profondeur de récursion : n niveaux pour Factorielle(n)
Mémoire supplémentaire : O(n) pour la pile d'appels
Comparaison avec version itérative : O(1) d'espace supplémentaire

Étape 3 : Version itérative optimisée
FONCTION FactorielleIter(n) : Entier
  resultat ← 1
  POUR i DE 2 A n FAIRE
    resultat ← resultat * i
  FIN POUR
  RETOURNER resultat
FIN

// Comparaison des performances
// FactorielleRec(1000): ~8KB de pile, risque de dépassement
// FactorielleIter(1000): ~100B, très efficace en mémoire
Étape 4 : Résultats de l'analyse
Version Complexité temporelle Complexité spatiale Risque
Récursive O(n) O(n) Dépassement de pile
Itérative O(n) O(1) Aucun
Version itérative : Même complexité temporelle mais meilleure gestion mémoire
Conclusion :

La version itérative est préférable en termes de consommation mémoire, même si les deux versions ont la même complexité temporelle.

Règles d'analyse mémoire :

Évaluer la consommation spatiale : Variables, structures de données

Considérer la récursion : Impact sur la pile d'appels

Optimiser quand possible : Remplacer la récursion par l'itération

Corrigé : Exercices 4 à 5
4 Optimisation fonction récursive
Définition :

Problème : Optimiser une fonction récursive avec mémorisation

Version naïve
⏰ O(2^n)
Calculs répétés
Version optimisée
⚡ O(n)
Mémorisation des résultats
Gain obtenu
🚀 1000x+
Amélioration spectaculaire
Étape 1 : Fonction récursive inefficace - Fibonacci
FONCTION Fibonacci(n) : Entier
  SI n = 0 OU n = 1 ALORS
    RETOURNER n
  SINON
    RETOURNER Fibonacci(n - 1) + Fibonacci(n - 2)
  FIN SI
FIN

// Problème: Fibonacci(5) calcule Fibonacci(3) plusieurs fois!
Étape 2 : Analyse des performances

Complexité temporelle : O(2^n) - Extrêmement inefficace
Calcul de Fibonacci(10) : 177 appels récursifs
Calcul de Fibonacci(30) : 2,692,537 appels récursifs!
Problème : Les mêmes sous-problèmes sont recalculés plusieurs fois

Étape 3 : Version optimisée avec mémorisation
FONCTION FibonacciMemo(n, memo={}) : Entier
  SI n IN memo ALORS
    RETOURNER memo[n]
  FIN SI
  
  SI n = 0 OU n = 1 ALORS
    resultat ← n
  SINON
    resultat ← FibonacciMemo(n - 1, memo) + FibonacciMemo(n - 2, memo)
  FIN SI
  
  memo[n] ← resultat
  RETOURNER resultat
FIN

// Résultat: Fibonacci(30) = 832040, mais avec seulement 30 appels!
Étape 4 : Comparaison des performances
n Fibonacci naïf Fibonacci mémorisé Gain
10 177 appels 10 appels 17.7x
20 21,891 appels 20 appels 1,095x
30 2,692,537 appels 30 appels 89,751x
Optimisation massive : O(2^n) → O(n) grâce à la mémorisation
Conclusion :

La mémorisation transforme radicalement les performances d'une fonction récursive mal conçue, passant d'une complexité exponentielle à une complexité linéaire.

Règles d'optimisation récursive :

Identifier les sous-problèmes répétés : Rechercher les calculs redondants

Utiliser la mémorisation : Stocker les résultats intermédiaires

Transformer en programmation dynamique : Approche bottom-up

5 Comparaison implémentations
Définition :

Problème : Comparer différentes implémentations d'une même fonctionnalité

1
Identifier les implémentations
2
Créer des scénarios de test
3
Mesurer les performances
4
Analyser les résultats
Étape 1 : Trois implémentations de recherche d'occurrences
FONCTION CompterOccurrences_v1(chaine, lettre) : Entier
  compteur ← 0
  POUR caractere IN chaine FAIRE
    SI caractere = lettre ALORS
      compteur ← compteur + 1
    FIN SI
  FIN POUR
  RETOURNER compteur
FIN

FONCTION CompterOccurrences_v2(chaine, lettre) : Entier
  RETOURNER LONGUEUR(FILTRER c IN chaine SI c = lettre)
FIN

FONCTION CompterOccurrences_v3(chaine, lettre) : Entier
  RETOURNER CHAINE_OCCURRENCES(chaine, lettre)  // Fonction native
FIN
Étape 2 : Résultats de performance (sur chaîne de 100,000 caractères)
Implémentation Temps (ms) Complexité Clarté Recommandation
v1 - Boucle 45.2ms O(n) Très claire Bonne pour apprentissage
v2 - Filtrage 38.7ms O(n) Assez claire Élégante et fonctionnelle
v3 - Native 8.3ms O(n) Moins claire Meilleure performance
Étape 3 : Analyse comparative

Implémentation v1 : Claire mais plus lente à cause de la boucle explicite
Implémentation v2 : Élégante mais crée une nouvelle liste intermédiaire
Implémentation v3 : Très rapide car optimisée en interne (langage bas niveau)
Compromis : Clarté vs Performance vs Lisibilité

Étape 4 : Recommandations d'utilisation

Pour l'apprentissage : Version 1 (explicite)
Pour le code de production : Version 3 (native)
Pour le code fonctionnel : Version 2 (élégante)
Conclusion : Le choix dépend du contexte et des priorités

Meilleure implémentation : Version native (8.3ms vs 45.2ms)
Conclusion :

La comparaison montre que l'implémentation native est 5 fois plus rapide que la version manuelle, soulignant l'importance de bien choisir ses outils selon le contexte.

Règles de comparaison d'implémentations :

Tester dans des conditions réalistes : Données représentatives

Considérer plusieurs critères : Performance, lisibilité, maintenance

Documenter les résultats : Pour référence future

Cours bien détaillé
Performance_{relative} = \frac{Temps_{référence}}{Temps_{candidat}} × 100\%
Mesure comparative de performance
⏱️
Temps d'exécution : Durée pour accomplir une tâche.
💾
Utilisation mémoire : Quantité de RAM utilisée.
📊
Complexité algorithmique : O(n), O(n²), O(log n), etc.
🔄
Scalabilité : Comment les performances changent avec la taille.
💡
Conseil : Mesurer les performances avec des jeux de données réalistes
🔍
Attention : Ne pas optimiser prématurément, d'abord identifier les goulets d'étranglement
Astuce : Utiliser des outils de profilage pour localiser les zones lentes
📋
Méthode : Comparer plusieurs implémentations pour trouver la meilleure
Vérification : S'assurer que l'optimisation ne change pas la logique du programme
Méthodes de mesure :
  • Chronométrage : Mesurer le temps d'exécution
  • Profilage mémoire : Analyser l'utilisation de la RAM
  • Complexité théorique : Analyse algorithmique
  • Test de charge : Évaluer avec des volumes croissants
Règles importantes :
  • La complexité théorique ne reflète pas toujours les performances réelles
  • Un algorithme optimal en temps peut utiliser trop de mémoire
  • Les performances peuvent varier selon les données d'entrée
  • Il faut toujours valider que l'optimisation ne casse pas la logique
Points clés à retenir :
  • Équilibre : Chercher le bon compromis entre performance, lisibilité et maintenance
  • Contexte : Adapter les mesures au contexte d'utilisation
  • Mesure objective : Utiliser des critères mesurables pour comparer les solutions
  • Amélioration continue : Réévaluer régulièrement les performances des solutions existantes
Mesurer les performances Tests et validation