18.2. Utilisation du module timeit

La chose la plus importante que vous devez savoir à propos de l'optimisation de code Python est que vous ne devez pas écrire vos propres fonctions de chronométrage.

Le chronométrage de petits segments de code est incroyablement complexe. Combien de temps processeur est consacré par votre ordinateur à l'exécution de code ? Y-a-t-il des choses tournant en tâche de fond ? En êtes vous sûr ? Tous les ordinateurs récents ont des processus s'exécutant en tâche de fond, certains tout le temps, d'autres de manière intermittente. Des tâches cron s'exécutent à intervalles réguliers, des services en tâche de fond se «réveillent» pour accomplir des tâches comme relever votre courrier électronique, se connecter à des serveurs de messagerie instantanée, vérifier les mises à jour disponibles, rechercher des virus, regarder si un disque a été inséré dans votre lecteur CD dans les 100 dernières nanosecondes etc. Avant de démarrer vos tests de chronométrage, fermez tous ces services et déconnectez-vous du réseau. Ensuite, fermez tout ce que vous avez oublié de fermer la première fois, puis fermez le service qui vérifie de manière incessante si vous êtes connecté au réseau, puis...

Il y a aussi la question des variations introduites par le framework de chronométrage lui-même. L'interpréteur Python a-t-il un cache des tables de méthodes ? Un cache des blocs de code compilés ? Des expressions régulières ? Votre code produit-il des effets de bord si il est exécuté plus d'une fois ? N'oubliez pas qu'il s'agit de fractions de secondes et que des petites erreurs dans votre framework de chronométrage produirons des distorsions irréparables des résultats

La communauté Python a un proverbe : «Python est livré avec les piles.» N'écrivez pas votre propre framework de chronométrage. Python 2.3 est livré avec un très bon framework appelé timeit.

Exemple 18.2. Présentation de timeit

Si vous ne l’avez pas déjà fait, vous pouvez télécharger cet exemple ainsi que les autres exemples du livre.

>>> import timeit
>>> t = timeit.Timer("soundex.soundex('Pilgrim')",
...     "import soundex")   1
>>> t.timeit()              2
8.21683733547
>>> t.repeat(3, 2000000)    3
[16.48319309109, 16.46128984923, 16.44203948912]
1 Le module timeit définit une classe, Timer, qui prend deux arguments. Les deux arguments sont des chaînes. Le premier argument est l'instruction que nous voulons chronométrer, dans le cas présent nous chronométrons un appel à la fonction Soundex du module soundex avec pour argument 'Pilgrim'. Le deuxième argument de la classe Timer est l'instruction d'importation qui met en place l'environnement de l'instruction. En interne, timeit met en place un environnement virtuel isolé, exécute l'instruction de mise en place (importation du module soundex), puis compile et exécute l'instruction à chronométrer (appel de la fonction Soundex).
2 Une fois que nous avons l'objet Timer, la chose la plus simple à faire est d'appeler timeit(), qui appelle notre fonction 1 million de fois et retourne le nombre de secondes que cela a pris.
3 L'autre méthode importante de l'objet Timer est repeat(), qui prend deux arguments optionnels. Le premier argument est le nombre de répétition du test et le second est le nombre de fois que l'instruction sera exécutée pour chaque test. Les deux arguments sont optionnels, leurs valeurs par défaut sont respectivement 3 et 1000000. La méthode repeat() retourne une liste du temps que chaque cycle de test a pris, en secondes.
ASTUCE
Vous pouvez utiliser le module timeit en ligne de commande pour tester un programme Python existant sans en modifier le code. Voir http://docs.python.org/lib/node396.html pour la documentation des paramètres de ligne de commande.

Notez que repeat() retourne une liste de temps. Les temps ne seront pratiquement jamais identiques, à cause des variations du temps processeur alloué à l'interpréteur Python (et de toutes les tâches de fond dont on ne peut pas se débarrasser). Il est tentant de se dire «Prenons la moyenne et considérons que c'est le nombre correct.»

En fait, c'est presque certainement faux. Les tests qui ont pris plus de temps ne l'ont pas fait à cause de variations dans notre code ou dans l'interpréteur Python, ils ont pris plus de temps à cause des tâches de fond, ou d'autres facteurs externes à l'interpréteur Python que nous ne pouvons pas entièrement éliminer. Si les différents résultats varient en pourcentage de plus de quelques points, il y a trop de variation pour que nous puissions nous y fier. Sinon, c'est le temps minimum qu'il faut prendre en compte et ne pas tenir compte du reste..

Python a une fonction min très utile qui prend une liste et retourne la valeur la plus petite de la liste :

>>> min(t.repeat(3, 1000000))
8.22203948912
ASTUCE
Le module timeit ne fonctionne que si vous savez déjà quelle partie de votre code optimiser. Si vous avez un programme Python plus grand et que vous ne savez pas où se trouve le problème de performances, allez voir le module hotshot.