Isoler le coût d’une ligne de code
Objectif : Localiser précisément une opération coûteuse dans le calcul des dépenses.
Quand une fonction apparaît dans un rapport de profilage, vous savez qu’elle contribue au temps d’exécution, mais vous ne savez pas encore quelle opération, à l’intérieur de cette fonction, est coûteuse. Pour l’outil de dépenses, il faut pouvoir distinguer le coût de l’accès au montant, de sa conversion et de son addition. Vous allez mesurer ces opérations ligne par ligne, sans modifier le calcul métier.
Ajouter une mesure locale au scénario
La leçon précédente a ajouté à scripts/profiler.py un benchmark reproductible et un rapport cProfile. Ici, vous conservez ces deux diagnostics et vous ajoutez line_profiler pour inspecter spécifiquement la fonction d’agrégation. Installez l’outil dans l’environnement de développement avec python -m pip install line_profiler ; cette dépendance sert au diagnostic et ne change pas le résultat métier.
Remplacez le contenu de scripts/profiler.py par le script complet ci-dessous. Le jeu déterministe contient 10 000 dépenses, chacune représentée par un montant en euros. Le benchmark garde sept mesures de 1 000 appels et affiche la médiane par appel ; le rapport cumulatif montre les fonctions contributrices, puis le profileur ligne par ligne mesure une seule exécution ciblée.
from cProfile import Profile
from pstats import SortKey, Stats
from statistics import median
from timeit import repeat
from typing import TypedDict
from line_profiler import LineProfiler
class Depense(TypedDict):
montant: float
def construire_depenses(nombre: int) -> list[Depense]:
return [
{"montant": float((indice % 97) + 1)}
for indice in range(nombre)
]
def aggreger_montants(depenses: list[Depense]) -> float:
total = 0.0
for depense in depenses:
total += depense["montant"]
return total
def main() -> None:
depenses = construire_depenses(10_000)
mesures = repeat(
"aggreger_montants(depenses)",
globals=globals(),
number=1_000,
repeat=7,
)
temps_par_appel = [duree / 1_000 for duree in mesures]
print(f"Médiane : {median(temps_par_appel):.9f} s par appel")
profil = Profile()
profil.runcall(aggreger_montants, depenses)
Stats(profil).strip_dirs().sort_stats(SortKey.CUMULATIVE).print_stats(10)
profileur_ligne = LineProfiler(aggreger_montants)
profileur_ligne(aggreger_montants)(depenses)
profileur_ligne.print_stats()
if __name__ == "__main__":
main()Lancez le scénario depuis la racine du projet avec python scripts/profiler.py. La sortie affiche d’abord le temps médian du benchmark, ensuite les statistiques cProfile, puis un tableau Line #, Hits, Time et Line Contents. Les numéros de ligne dépendent du fichier ; le tableau identifie les lignes exécutées et leur temps mesuré pendant l’appel instrumenté.
Lire les mesures sans surinterpréter
Hits indique combien de fois une ligne a été exécutée. Time rapporte le temps total mesuré sur cette ligne, tandis que Per Hit donne une moyenne par exécution ; % Time situe sa part dans la fonction profilée. Dans cet exemple, la ligne d’addition est exécutée 10 000 fois : un coût individuel minime peut donc représenter une part notable du temps total.
Le tableau ne constitue pas une mesure de production exacte. L’instrumentation ajoute un coût et peut modifier les proportions observées, en particulier pour des opérations très courtes. Utilisez donc ce résultat pour choisir une zone à examiner, puis appuyez-vous sur le benchmark non instrumenté pour évaluer le temps réel du scénario, comme dans les leçons précédentes.
Le script ne modifie ni les dépenses ni la fonction d’agrégation pour accélérer artificiellement le scénario. Si une ligne semble coûteuse, notez le constat et vérifiez qu’il est cohérent avec le profil cProfile et le benchmark. La prochaine leçon portera sur la réduction d’un coût confirmé par les mesures ; vous y comparerez le comportement avant et après une modification.
À retenir
- Installez line_profiler dans l’environnement de développement pour profiler des lignes ciblées.
- Utilisez le tableau pour repérer les opérations qui consomment du temps et tenez compte de leur nombre d’exécutions.
- Interprétez les temps ligne par ligne comme un diagnostic instrumenté, pas comme une mesure de référence.
- Conservez le benchmark reproductible pour vérifier le temps réel avant toute optimisation.