27.5. "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.


27.5.1. 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 3: 30.2 usec per loop
   $ python3 -m timeit '"-".join([str(n) for n in range(100)])'
   10000 loops, best of 3: 27.5 usec per loop
   $ python3 -m timeit '"-".join(map(str, range(100)))'
   10000 loops, best of 3: 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

Note however that "timeit" will automatically determine the number of
repetitions only when the command-line interface is used.  In the
Exemples section you can find more advanced examples.


27.5.2. 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=3, 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é.

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:

        By default, "timeit()" temporarily turns off *garbage
        collection* during the timing.  The advantage of this approach
        is that it makes independent timings more comparable.  This
        disadvantage is that GC may be an important component of the
        performance of the function being measured.  If so, GC can be
        re-enabled as the first statement in the *setup* string.  For
        example:

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

   autorange(callback=None)

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

      This is a convenience function that calls "timeit()" repeatedly
      so that the total time >= 0.2 second, returning the eventual
      (number of loops, time taken for that number of loops). It calls
      "timeit()" with *number* set to successive powers of ten (10,
      100, 1000, ...) up to a maximum of one billion, until the time
      taken is at least 0.2 second, or the maximum is reached.

      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=3, 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.

   print_exc(file=None)

      Outil permettant d'afficher la trace du code chronométré.

      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".


27.5.3. 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] [-t] [-c] [-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

   how many times to repeat the timer (default 3)

-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.

-t, --time

   use "time.time()" (deprecated)

-u, --unit=U

      specify a time unit for timer output; can select usec, msec, or
      sec

   Nouveau dans la version 3.5.

-c, --clock

   use "time.clock()" (deprecated)

-v, --verbose

   affiche les temps d'exécutions bruts, répéter pour plus de
   précision

-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.

Si "-n" n'est pas donnée, le nombre de boucles adapté est déterminé
automatiquement en essayant les puissances de 10 successives jusqu'à
ce que le temps total d'exécution dépasse 0,2 secondes.

"default_timer()" measurements can be affected by other programs
running on the same machine, so the best thing to do when accurate
timing is necessary is to repeat the timing a few times and use the
best time.  The "-r" option is good for this; the default of 3
repetitions is probably enough in most cases.  You can use
"time.process_time()" to measure CPU time.

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.


27.5.4. 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'
   10000000 loops, best of 3: 0.0877 usec per loop
   $ python -m timeit -s 'text = "sample string"; char = "g"'  'text.find(char)'
   1000000 loops, best of 3: 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.40193588800002544, 0.3960157959998014, 0.39594301399984033]

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'
   100000 loops, best of 3: 15.7 usec per loop
   $ python -m timeit 'if hasattr(str, "__bool__"): pass'
   100000 loops, best of 3: 4.26 usec per loop

   $ python -m timeit 'try:' '  int.__bool__' 'except AttributeError:' '  pass'
   1000000 loops, best of 3: 1.43 usec per loop
   $ python -m timeit 'if hasattr(int, "__bool__"): pass'
   100000 loops, best of 3: 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()))
