"timeit" — Mesurer le temps d'exécution de fragments de code
************************************************************

**Code source :** Lib/timeit.py

======================================================================

Ce module fournit une façon simple de mesurer le temps d'exécution de
fragments de code Python. Il expose une Interface en ligne de commande
ainsi qu'une interface Python. Ce module permet d'éviter un certain
nombre de problèmes classiques liés à la mesure des temps d'exécution.
Voir par exemple à ce sujet l'introduction par Tim Peters du chapitre
« Algorithmes » dans le livre *Python Cookbook*, aux éditions
O'Reilly.


Exemples simples
================

L'exemple suivant illustre l'utilisation de l'Interface en ligne de
commande afin de comparer trois expressions différentes :

   $ python3 -m timeit '"-".join(str(n) for n in range(100))'
   10000 loops, best of 5: 30.2 usec per loop
   $ python3 -m timeit '"-".join([str(n) for n in range(100)])'
   10000 loops, best of 5: 27.5 usec per loop
   $ python3 -m timeit '"-".join(map(str, range(100)))'
   10000 loops, best of 5: 23.2 usec per loop

L'Interface Python peut être utilisée aux mêmes fins avec :

   >>> import timeit
   >>> timeit.timeit('"-".join(str(n) for n in range(100))', number=10000)
   0.3018611848820001
   >>> timeit.timeit('"-".join([str(n) for n in range(100)])', number=10000)
   0.2727368790656328
   >>> timeit.timeit('"-".join(map(str, range(100)))', number=10000)
   0.23702679807320237

Un objet appelable peut également être passé en argument à l'Interface
Python :

   >>> timeit.timeit(lambda: "-".join(map(str, range(100))), number=10000)
   0.19665591977536678

Notez cependant que "timeit()" détermine automatiquement le nombre de
répétitions seulement lorsque l'interface en ligne de commande est
utilisée. Vous pouvez trouver des exemples d'usages avancés dans la
section Exemples.


Interface Python
================

Ce module définit une classe publique ainsi que trois fonctions
destinées à simplifier son usage :

timeit.timeit(stmt='pass', setup='pass', timer=<default timer>, number=1000000, globals=None)

   Crée une instance d'objet "Timer" à partir de l'instruction donnée,
   du code *setup* et de la fonction *timer*, puis exécute sa méthode
   "timeit()" à *number* reprises. L'argument optionnel *globals*
   spécifie un espace de nommage dans lequel exécuter le code.

   Modifié dans la version 3.5: Le paramètre optionnel *globals* a été
   ajouté.

timeit.repeat(stmt='pass', setup='pass', timer=<default timer>, repeat=5, number=1000000, globals=None)

   Crée une instance d'objet "Timer" à partir de l'instruction donnée,
   du code *setup* et de la fonction *timer*, puis exécute sa méthode
   "repeat()" à *number* reprises, *repeat* fois. L'argument optionnel
   *globals* spécifie un espace de nommage dans lequel exécuter le
   code.

   Modifié dans la version 3.5: Le paramètre optionnel *globals* a été
   ajouté.

   Modifié dans la version 3.7: La valeur par défaut de *repeat* est
   passée de 3 à 5.

timeit.default_timer()

   Le minuteur par défaut, qui est toujours "time.perf_counter()".

   Modifié dans la version 3.3: "time.perf_counter()" est désormais le
   minuteur par défaut.

class timeit.Timer(stmt='pass', setup='pass', timer=<timer function>, globals=None)

   Classe permettant de mesurer le temps d'exécution de fragments de
   code.

   Ce constructeur prend en argument une instruction dont le temps
   d'exécution doit être mesuré, une instruction additionnelle de mise
   en place et une fonction de chronométrage. Les deux instructions
   valent "'pass'" par défaut; la fonction de chronométrage dépend de
   la plateforme d'exécution (se référer au *doc string* du module).
   *stmt* et *setup* peuvent contenir plusieurs instructions séparées
   par des ";" ou des sauts de lignes tant qu'ils ne comportent pas de
   littéraux sur plusieurs lignes. L'instruction est exécutée dans
   l'espace de nommage de *timeit* par défaut ; ce comportement peut
   être modifié en passant un espace de nommage au paramètre
   *globals*.

   Pour mesurer le temps d'exécution de la première instruction,
   utilisez la méthode "timeit()". Les méthodes "repeat()" et
   "autorange()" sont des méthodes d'agrément permettant d'appeler
   "timeit()" à plusieurs reprises.

   Le temps d'exécution de *setup* n'est pas pris en compte dans le
   temps global d'exécution.

   Les paramètres *stmt* et *setup* peuvent également recevoir des
   objets appelables sans argument. Ceci transforme alors les appels à
   ces objets en fonction de chronométrage qui seront exécutées par
   "timeit()". Notez que le surcoût lié à la mesure du temps
   d'exécution dans ce cas est légèrement supérieur en raisons des
   appels de fonction supplémentaires.

   Modifié dans la version 3.5: Le paramètre optionnel *globals* a été
   ajouté.

   timeit(number=1000000)

      Mesure le temps *number* exécution de l'instruction principale.
      Ceci exécute l'instruction de mise en place une seule fois puis
      renvoie un flottant correspondant au temps nécessaire à
      l'exécution de l'instruction principale à plusieurs reprises,
      mesuré en secondes. L'argument correspond au nombre d'itérations
      dans la boucle, par défaut un million. L'instruction principale,
      l'instruction de mise en place et la fonction de chronométrage
      utilisée sont passées au constructeur.

      Note:

        Par défaut, "timeit()" désactive temporairement le *ramasse-
        miettes* pendant le chronométrage. Cette approche a l'avantage
        de permettre de comparer des mesures indépendantes.
        L'inconvénient de cette méthode est que le ramasse-miettes
        peut avoir un impact significatif sur les performances de la
        fonction étudiée. Dans ce cas, le ramasse-miettes peut être
        réactivé en première instruction de la chaîne *setup*. Par
        exemple :

           timeit.Timer('for i in range(10): oct(i)', 'gc.enable()').timeit()

   autorange(callback=None)

      Détermine automatiquement combien de fois appeler "timeit()".

      Cette fonction d'agrément appelle "timeit()" à plusieurs
      reprises jusqu'à ce que le temps total écoulé soit supérieur à
      0,2 secondes et renvoie le couple (nombre de boucles, temps
      nécessaire pour exécuter ce nombre de boucles). Elle appelle
      "timeit()" avec un nombre d'itérations croissant selon la
      séquence 1, 2, 5, 10, 20, 50, … jusqu'à ce que le temps
      d'exécution dépasse 0,2 secondes.

      Si *callback* est spécifié et n'est pas "None", elle est appelée
      après chaque itération avec deux arguments (numéro de
      l'itération et temps écoulé) : "callback(number, time_taken)".

      Nouveau dans la version 3.6.

   repeat(repeat=5, number=1000000)

      Appelle "timeit()" plusieurs fois.

      Cette fonction d'agrément appelle "timeit()" à plusieurs
      reprises et renvoie une liste de résultats. Le premier argument
      spécifie le nombre d'appels à "timeit()". Le second argument
      spécifie l'argument *number* de "timeit()".

      Note:

        Il est tentant de vouloir calculer la moyenne et l'écart-type
        des résultats et notifier ces valeurs. Ce n'est cependant pas
        très utile. En pratique, la valeur la plus basse donne une
        estimation basse de la vitesse maximale à laquelle votre
        machine peut exécuter le fragment de code spécifié ; les
        valeurs hautes de la liste sont typiquement provoquées non pas
        par une variabilité de la vitesse d'exécution de Python, mais
        par d'autres processus interférant avec la précision du
        chronométrage. Le "min()" du résultat est probablement la
        seule valeur à laquelle vous devriez vous intéresser. Pour
        aller plus loin, vous devriez regarder l'intégralité des
        résultats et utiliser le bon sens plutôt que les statistiques.

      Modifié dans la version 3.7: La valeur par défaut de *repeat*
      est passée de 3 à 5.

   print_exc(file=None)

      Helper to print a traceback from the timed code.

      Usage typique :

         t = Timer(...)       # outside the try/except
         try:
             t.timeit(...)    # or t.repeat(...)
         except Exception:
             t.print_exc()

      L'avantage par rapport à la trace standard est que les lignes
      sources du code compilé sont affichées. Le paramètre optionnel
      *file* définit l'endroit où la trace est envoyée, par défaut
      "sys.stderr".


Interface en ligne de commande
==============================

Lorsque le module est appelé comme un programme en ligne de commande,
la syntaxe suivante est utilisée :

   python -m timeit [-n N] [-r N] [-u U] [-s S] [-h] [statement ...]

Les options suivantes sont gérées :

-n N, --number=N

   nombre d'exécutions de l'instruction *statement*

-r N, --repeat=N

   nombre de répétitions du chronomètre (5 par défaut)

-s S, --setup=S

   instruction exécutée une seule fois à l'initialisation ("pass" par
   défaut)

-p, --process

   mesure le temps au niveau du processus et non au niveau du système,
   en utilisant "time.process_time()" plutôt que "time.perf_counter()"
   qui est utilisée par défaut

   Nouveau dans la version 3.3.

-u, --unit=U

      spécifie l'unité de temps utilisée pour la sortie du chronomètre
      (parmi *nsec*, *usec*, *msec* ou *sec*)

   Nouveau dans la version 3.5.

-v, --verbose

   print raw timing results; repeat for more digits precision

-h, --help

   affiche un court message d'aide puis quitte

Une instruction sur plusieurs lignes peut être donnée en entrée en
spécifiant chaque ligne comme un argument séparé. Indenter une ligne
est possible en encadrant l'argument de guillemets et en le préfixant
par des espaces. Plusieurs "-s" sont gérées de la même façon.

If "-n" is not given, a suitable number of loops is calculated by
trying increasing numbers from the sequence 1, 2, 5, 10, 20, 50, ...
until the total time is at least 0.2 seconds.

Les mesures de "default_timer()" peuvent être altérées par d'autres
programmes s'exécutant sur la même machine. La meilleure approche
lorsqu'un chronométrage exact est nécessaire est de répéter celui-ci à
plusieurs reprises et considérer le meilleur temps. L'option "-r" est
adaptée à ce fonctionnement, les cinq répétitions par défaut suffisent
probablement dans la plupart des cas. Vous pouvez utiliser
"time.process_time()" pour mesurer le temps processeur.

Note:

  Il existe un surcoût minimal associé à l'exécution de l'instruction
  *pass*. Le code présenté ici ne tente pas de le masquer, mais vous
  devez être conscient de son existence. Ce surcoût minimal peut être
  mesuré en invoquant le programme sans argument ; il peut différer en
  fonction des versions de Python.


Exemples
========

Il est possible de fournir une instruction de mise en place exécutée
une seule fois au début du chronométrage :

   $ python -m timeit -s 'text = "sample string"; char = "g"'  'char in text'
   5000000 loops, best of 5: 0.0877 usec per loop
   $ python -m timeit -s 'text = "sample string"; char = "g"'  'text.find(char)'
   1000000 loops, best of 5: 0.342 usec per loop

   >>> import timeit
   >>> timeit.timeit('char in text', setup='text = "sample string"; char = "g"')
   0.41440500499993504
   >>> timeit.timeit('text.find(char)', setup='text = "sample string"; char = "g"')
   1.7246671520006203

La même chose peut être réalisée en utilisant la classe "Timer" et ses
méthodes :

   >>> import timeit
   >>> t = timeit.Timer('char in text', setup='text = "sample string"; char = "g"')
   >>> t.timeit()
   0.3955516149999312
   >>> t.repeat()
   [0.40183617287970225, 0.37027556854118704, 0.38344867356679524, 0.3712595970846668, 0.37866875250654886]

Les exemples qui suivent montrent comment chronométrer des expressions
sur plusieurs lignes. Nous comparons ici le coût d'utilisation de
"hasattr()" par rapport à "try"/"except" pour tester la présence ou
l'absence d'attributs d'un objet :

   $ python -m timeit 'try:' '  str.__bool__' 'except AttributeError:' '  pass'
   20000 loops, best of 5: 15.7 usec per loop
   $ python -m timeit 'if hasattr(str, "__bool__"): pass'
   50000 loops, best of 5: 4.26 usec per loop

   $ python -m timeit 'try:' '  int.__bool__' 'except AttributeError:' '  pass'
   200000 loops, best of 5: 1.43 usec per loop
   $ python -m timeit 'if hasattr(int, "__bool__"): pass'
   100000 loops, best of 5: 2.23 usec per loop

   >>> import timeit
   >>> # attribute is missing
   >>> s = """\
   ... try:
   ...     str.__bool__
   ... except AttributeError:
   ...     pass
   ... """
   >>> timeit.timeit(stmt=s, number=100000)
   0.9138244460009446
   >>> s = "if hasattr(str, '__bool__'): pass"
   >>> timeit.timeit(stmt=s, number=100000)
   0.5829014980008651
   >>>
   >>> # attribute is present
   >>> s = """\
   ... try:
   ...     int.__bool__
   ... except AttributeError:
   ...     pass
   ... """
   >>> timeit.timeit(stmt=s, number=100000)
   0.04215312199994514
   >>> s = "if hasattr(int, '__bool__'): pass"
   >>> timeit.timeit(stmt=s, number=100000)
   0.08588060699912603

Afin de permettre à "timeit" d'accéder aux fonctions que vous avez
définies, vous pouvez passer au paramètre *setup* une instruction
d'importation :

   def test():
       """Stupid test function"""
       L = [i for i in range(100)]

   if __name__ == '__main__':
       import timeit
       print(timeit.timeit("test()", setup="from __main__ import test"))

Une autre possibilité est de passer "globals()" au paramètre
*globals*, ceci qui exécutera le code dans l'espace de nommage global
courant. Cela peut être plus pratique que de spécifier manuellement
des importations :

   def f(x):
       return x**2
   def g(x):
       return x**4
   def h(x):
       return x**8

   import timeit
   print(timeit.timeit('[func(42) for func in (f,g,h)]', globals=globals()))
