Depuis que j’ai commencé à optimiser des scripts pour des sites à fort trafic, j’ai appris à privilégier des mesures fiables plutôt que des impressions. Dans cet article je montre, pas à pas, comment utiliser le module timeit pour obtenir des mesures reproductibles du temps d’exécution d’un bout de code, comment visualiser un benchmark simple et comment en tirer des décisions d’optimisation. Je m’appuie sur un cas concret — trois implémentations de la suite de Fibonacci — pour mettre en lumière les différences d’efficacité entre méthodes itératives, récursives et dynamiques. Vous trouverez des exemples de code prêts à l’emploi, des astuces pour éviter les pièges du profilage et des conseils pratiques pour transformer des observations en gains réels de performance sur vos *scripts* Python.
En bref :
- timeit permet de mesurer précisément le temps d’exécution d’extraits de code Python.
- Comparer plusieurs implémentations sur des jeux de données croissants révèle la complexité pratique (ex. exponentielle vs linéaire).
- Visualiser les mesures avec *matplotlib* transforme un benchmark en décision d’optimisation.
- Astuce : désactiver le GC et utiliser timeit.repeat pour des mesures stables.
- Ressources complémentaires et tutoriels avancés disponibles sur *Numereeks*.
Réponse rapide : Utilisez timeit.timeit(stmt, number=…, globals=globals()) pour mesurer le temps d’exécution d’une fonction, répétez avec timeit.repeat pour un benchmark robuste, puis tracez les temps avec *matplotlib* pour visualiser les tendances et décider d’une stratégie d’optimisation.
Utiliser timeit pour mesurer le temps d’exécution d’un script Python
Avec quelques commandes simples, vous obtenez une mesure de performance fiable pour des fragments de code. Je commence toujours par importer les modules de base : import matplotlib.pyplot as plt et import timeit. Ensuite, j’isole la portion de code à tester et j’appelle timeit.timeit ou timeit.repeat.
- timeit.timeit(stmt, number=…) exécute stmt un certain nombre de fois et retourne le temps total.
- timeit.repeat(…, repeat=…) renvoie plusieurs mesures pour calculer la variance.
- timeit.default_timer() fournit le timer performant adapté à votre OS.
Exemple concret : pour mesurer 1000 appels à une fonction foo avec l’environnement courant, j’utilise timeit.timeit(‘foo(10)’, number=1000, globals=globals()). Cette pratique évite les biais liés aux appels isolés et aux fluctuations système.
- Astuce pratique : encapsulez la logique de setup (imports, création d’objets) dans l’argument setup si vous utilisez la ligne de commande.
- Pensez à exclure toute opération d’entrée/sortie (I/O) pendant le benchmark.

Syntaxe, paramètres et bonnes pratiques rapides
La syntaxe de base est simple : timeit.timeit(stmt, setup, timer, number). Les paramètres principaux sont :
- stmt : le code testé (souvent une expression ou un appel de fonction).
- setup : code exécuté avant stmt (utile pour imports).
- number : nombre d’exécutions pour accumuler le temps.
- repeat (pour timeit.repeat) : nombre de répétitions indépendantes pour estimer la variance.
Je recommande d’augmenter number pour des extraits très courts, et d’utiliser timeit.repeat pour obtenir une fourchette de temps au lieu d’une valeur isolée.
- Exemple ligne de commande : python -m timeit -n 1000 -r 5 -s ‘import math’ ‘math.sqrt(2)’.
- Astuce : évitez de mesurer des appels contenant des allocations massives qui fausseraient le résultat si le but est de tester l’algorithme.
Exemples pratiques : comparer trois implémentations de Fibonacci
Pour illustrer, j’ai testé trois versions de Fibonacci : itérative, récursive naïve et récursive mémoïsée (dynamique). Les différences de performance sont significatives et pédagogiques pour tout profilage de code.
- Version itérative : efficace en temps et mémoire pour de grandes n.
- Version récursive naïve : croissance exponentielle du temps d’exécution.
- Version dynamique : utilise la mémoire pour éviter les recalculs et est quasi-linéaire.
Exemples de fonctions (version abrégée, à coller dans un script) :
- fibo_iter(n): initialise f0,f1 et boucle pour obtenir Fn.
- fibo_rec(n): cas de base 0/1, sinon fibo_rec(n-1)+fibo_rec(n-2).
- fibo_dyn(n, memo={0:0,1:1}): vérifie memo puis calcule et stocke.
J’utilise ensuite timeit.timeit(‘fibo_iter(15)’, number=100, globals=globals()) pour recueillir des temps comparables. Les résultats montrent clairement l’inefficacité de la récursion naïve sur des n croissants.
Visualiser les résultats : tracer un benchmark avec matplotlib
Visualiser les temps transforme des nombres en décisions : on voit si la courbe est linéaire, quadratique ou exponentielle. J’ai tracé des courbes pour n variant de 0 à 19 (puis jusqu’à 99 pour itératif/dynamique) pour comparer les tendances.
- Étape 1 : préparer les abscisses (liste de n).
- Étape 2 : pour chaque fonction, appeler timeit.timeit(…, number=100).
- Étape 3 : tracer avec plt.plot et afficher.
Extrait de script utilisé pour la collecte (synthétisé) :
- abscisses = [k for k in range(0, 20)]
- ord_iter.append(timeit.timeit(‘fibo_iter(x)’, number=100, globals=globals()))
- plt.plot(abscisses, ord_iter, ‘b’)
Observation importante : la version récursive naïve révèle une croissance quasi-exponentielle du temps d’exécution, tandis que les courbes itérative et dynamique sont proches, la dynamique étant souvent la plus rapide.

Interpréter la courbe et convertir en optimisation
Une courbe exponentielle signale immédiatement un besoin d’optimisation algorithmique. Pour moi, cela signifie soit changer d’algorithme, soit ajouter de la mémorisation ou recourir à des bibliothèques optimisées.
- Si la courbe est plate ou linéaire : gains marginaux attendus en micro-optimisations.
- Si la courbe explose : revoir la logique (par ex. remplacer récursion naïve par itératif ou dynamique).
- Mesures complémentaires : utiliser un profileur (cProfile) pour localiser les hotspots.
En pratique, j’ai transformé plusieurs scripts clients en remplaçant des boucles Python pure par des appels vectorisés via *NumPy* ou des structures adaptées, diminuant le temps d’exécution de manière substantielle.
Bonnes pratiques pour un benchmark fiable et reproductible
Un benchmark utile est reproductible, isolé et interprété : suivez une méthodologie simple. J’applique toujours ces règles quand je fais du profilage pour éviter des conclusions hâtives.
- Désactivez le garbage collector pendant le test si nécessaire (pour réduire le bruit).
- Utilisez timeit.repeat et prenez le minimum ou la médiane des résultats.
- Évitez les opérations I/O et les accès réseau pendant la mesure.
- Testez sur des jeux de données croissants pour détecter la complexité réelle.
- Documentez l’environnement (CPU, OS, version de *Python*) pour la reproductibilité.
Je note systématiquement la machine et la version de *Python* dans mes rapports de benchmark. Cela évite d’attribuer à tort une régression de code à un simple changement d’environnement.
- Astuce SEO/technique : conservez les scripts de test dans le dépôt pour audit futur.
- Utilisez des visualisations pour présenter des résultats à des non-développeurs — c’est toujours convaincant.
Outils complémentaires et options en ligne de commande
timeit fournit aussi une interface CLI pratique : python -m timeit -n N -r R -s ‘setup’ ‘code’. C’est utile pour des tests rapides sans écrire de script complet.
- Options : -n nombre d’exécutions, -r répétitions, -s setup.
- Timers : -t pour time.time(), -c pour time.clock() (selon plateforme).
- Mesure fine : utilisez timeit.default_timer() pour marquer un point précis dans un script.
Pour les cas complexes, combinez timeit et des profileurs (cProfile, pyinstrument) afin d’obtenir à la fois des métriques globales et des hotspots détaillés.
Ressources, lectures et tutoriels complémentaires
J’ai extrait et adapté plusieurs fiches pratiques pour accélérer votre apprentissage. Ces ressources complètent le guide et expliquent des sujets proches comme les structures de données ou les décorateurs.
- Guide sur les *dictionnaires* et astuces avancées : dictionnaires Python – astuces
- Rappels mathématiques utiles en code : racines et puissances en Python
- Travailler avec des tableaux et optimiser les opérations : tableaux, matrices et NumPy/pandas
- Comprendre les décorateurs et méthodes de classes : décorateurs, property et staticmethod
- Approfondissement : dictionnaires Python – astuces avancées
Ces lectures m’ont souvent aidé à choisir la bonne structure de données ou une technique d’optimisation adaptée au cas réel, plutôt que d’essayer des micro-optimisations sans impact.

Checklist rapide avant de lancer un benchmark
- Isoler l’extrait de code à tester.
- Fixer number et repeat adaptés à la durée des exécutions.
- Désactiver le GC si nécessaire et éviter les I/O.
- Consigner l’environnement (CPU, OS, Python).
- Tracer les résultats pour visualiser la tendance.
Phrase-clé : un bon benchmark n’est pas seulement technique, c’est aussi un récit chiffré qui justifie une optimisation.
Quand utiliser timeit plutôt qu’un profileur comme cProfile ?
Utilisez timeit pour mesurer précisément le temps d’exécution d’un petit extrait ou pour comparer plusieurs implémentations. Utilisez cProfile pour analyser le détail des appels et repérer les fonctions les plus coûteuses. Les deux outils sont complémentaires.
Combien de répétitions choisir pour timeit.repeat ?
Choisissez une valeur de repeat (par ex. 5) et un nombre d’exécutions (number) suffisamment élevé pour que chaque mesure dépasse le bruit système. Prenez la médiane ou le minimum des mesures pour plus de robustesse.
Dois-je désactiver le garbage collector pendant les tests ?
Désactiver le GC peut réduire la variance pour des tests courts et répétitifs. Cependant, réactivez-le ensuite. Documentez toujours cette manipulation dans votre rapport de benchmark.
Comment interpréter une courbe de temps d’exécution ?
Une courbe exponentielle indique un problème algorithmique (ex. récursion naïve), une courbe linéaire ou logarithmique signale des performances acceptables. Utilisez ces indices pour prioriser les optimisations.

