Repérer les fonctions coûteuses
Objectif : Identifier les fonctions qui consomment le plus de temps sur un traitement complet.
Un temps d’exécution total ne vous dit pas où votre script passe ses secondes. Vous pourriez optimiser une fonction visible dans le code, alors qu’une autre consomme l’essentiel du temps sur le traitement complet. Dans cette leçon, vous allez ajouter un profilage avec cProfile au scénario reproductible de l’outil de dépenses.
Lire un profil de fonctions
cProfile enregistre les appels de fonctions effectués pendant un scénario et le temps associé à ces appels. La bibliothèque standard pstats permet ensuite de trier et d’afficher les statistiques. Le tri par temps cumulé met en évidence les fonctions dont le coût inclut aussi celui des fonctions qu’elles appellent : c’est utile pour comprendre les principales contributions au parcours complet.
Le script ci-dessous conserve le benchmark établi précédemment : il crée 10 000 dépenses déterministes, mesure sept fois 1 000 agrégations et affiche la médiane du temps par appel. Il ajoute un scénario de profilage distinct, qui exécute plusieurs fois la même agrégation afin que les appels soient visibles dans le rapport. L’agrégation renvoie ici la somme des montants en centimes ; cette représentation entière évite les imprécisions de calcul propres aux nombres décimaux.
from __future__ import annotations
import cProfile
import pstats
import statistics
import timeit
from typing import TypedDict
class Depense(TypedDict):
montant_centimes: int
def construire_depenses(nombre: int) -> list[Depense]:
"""Crée un jeu déterministe de dépenses en centimes."""
return [
{"montant_centimes": index % 5000 + 1}
for index in range(nombre)
]
def total_depenses(depenses: list[Depense]) -> int:
"""Calcule le total des dépenses en centimes."""
return sum(depense["montant_centimes"] for depense in depenses)
def profiler_aggregation(depenses: list[Depense]) -> None:
"""Répète l'agrégation pour rendre ses appels lisibles dans le profil."""
for _ in range(100):
total_depenses(depenses)
def main() -> None:
depenses = construire_depenses(10_000)
total = total_depenses(depenses)
mesures = timeit.repeat(
stmt="total_depenses(depenses)",
globals={"total_depenses": total_depenses, "depenses": depenses},
number=1_000,
repeat=7,
)
mediane_par_appel = statistics.median(mesures) / 1_000
print(f"Dépenses traitées : {len(depenses)}")
print(f"Total : {total} centimes")
print(f"Médiane : {mediane_par_appel:.6f} seconde par agrégation")
print("\nProfil cProfile, trié par temps cumulé :")
profil = cProfile.Profile()
profil.enable()
profiler_aggregation(depenses)
profil.disable()
statistiques = pstats.Stats(profil)
statistiques.strip_dirs().sort_stats("cumulative").print_stats(10)
if __name__ == "__main__":
main()Exécutez le script depuis la racine du projet avec python scripts/profiler.py. Le programme affiche le nombre de dépenses, leur total, la médiane du benchmark, puis un tableau contenant notamment les appels, le temps propre et le temps cumulé. La sortie varie selon la machine et la charge du système : les durées ne constituent donc pas des valeurs attendues fixes.
Interpréter les fonctions coûteuses
Dans le tableau, repérez d’abord les lignes de votre propre code, comme profiler_aggregation et total_depenses. Le temps cumulé de profiler_aggregation inclut celui de l’agrégation appelée à chaque répétition. Le temps cumulé de total_depenses comprend notamment le parcours de la liste et la somme ; les fonctions internes de Python peuvent également apparaître dans le rapport.
Le temps propre est le temps passé dans une fonction sans celui de ses appels imbriqués ; le temps cumulé additionne ces coûts. Pour repérer une fonction qui pèse dans le parcours, examinez son temps cumulé et le nombre d’appels, puis vérifiez qu’elle appartient bien au scénario que vous cherchez à comprendre. Un temps cumulé élevé peut simplement refléter beaucoup d’appels à une fonction rapide, et pas nécessairement une fonction lente à chaque appel.
Ici, le profil sert à observer le coût du scénario qui agrège la liste complète de dépenses ; il ne prouve pas qu’une optimisation est nécessaire. Conservez les résultats et la configuration du scénario pour pouvoir les comparer lorsque vous étudierez une mesure plus locale. La prochaine leçon isolera le coût d’une ligne de code avec un profilage adapté à cette échelle.
À retenir
- cProfile enregistre les appels de fonctions et leur coût pendant un scénario réel.
- Triez par temps cumulé pour repérer les fonctions qui contribuent le plus au parcours complet.
- Lisez ensemble le temps cumulé, le temps propre et le nombre d’appels.
- Le profilage ajoute un surcoût : utilisez-le pour localiser les coûts, pas pour comparer des durées brutes au benchmark.
- Mesurez avant d’optimiser et vérifiez que le scénario profilé correspond au traitement visé.