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()))