402 votes

Comment utiliser le module timeit

Je comprends le concept de ce que timeit mais je ne suis pas sûr de savoir comment l'implémenter dans mon code.

Comment puis-je comparer deux fonctions, par exemple insertion_sort y tim_sort avec timeit ?

303voto

Sven Marnach Points 133943

Si vous voulez utiliser timeit dans une session Python interactive, il existe deux options pratiques :

  1. Utilisez le IPython coquille. Il est doté de la pratique %timeit fonction spéciale :

    In [1]: def f(x):
       ...:     return x*x
       ...: 
    
    In [2]: %timeit for x in range(100): f(x)
    100000 loops, best of 3: 20.3 us per loop
  2. Dans un interpréteur Python standard, vous pouvez accéder aux fonctions et aux autres noms que vous avez définis plus tôt au cours de la session interactive en les important à partir des éléments suivants __main__ dans la déclaration d'installation :

    >>> def f(x):
    ...     return x * x 
    ... 
    >>> import timeit
    >>> timeit.repeat("for x in range(100): f(x)", "from __main__ import f",
                      number=100000)
    [2.0640320777893066, 2.0876040458679199, 2.0520210266113281]

109 votes

+1 pour avoir montré le from __main__ import f technique. Je ne pense pas qu'elle soit aussi connue qu'elle devrait l'être. Elle est utile dans des cas comme celui-ci où un appel de fonction ou de méthode est chronométré. Dans d'autres cas (chronométrage d'une série d'étapes), elle est moins utile car elle introduit une surcharge d'appel de fonction.

17 votes

Vous pouvez juste faire %timeit f(x)

0 votes

Note : la configuration "import f" fait de l'accès à f une lecture locale rapide - ce qui ne reflète pas exactement un appel de fonction globale (de fonction rapide courte) dans le code normal typique. Dans Py3.5+, de vrais globaux peuvent être fournis : "Changed in version 3.5 : The optional globals parameter was added." ; Avant les globaux du module timeit étaient inévitables (ce qui n'a pas beaucoup de sens). Eventuellement les globaux du code appelant ( sys._getframe(N).f_globals ) aurait dû être la valeur par défaut dès le départ.

299voto

Raymond Hettinger Points 50330

La manière timeit consiste à exécuter une fois le code de configuration, puis à faire des appels répétés à une série d'instructions. Ainsi, si vous voulez tester le tri, il faut faire attention à ce qu'un passage à un tri sur place n'affecte pas le passage suivant avec des données déjà triées (ce qui, bien sûr, rendrait l'option Timsort brille vraiment parce qu'elle est plus performante lorsque les données sont déjà partiellement ordonnées).

Voici un exemple de mise en place d'un test de tri :

>>> import timeit

>>> setup = '''
import random

random.seed('slartibartfast')
s = [random.random() for i in range(1000)]
timsort = list.sort
'''

>>> print min(timeit.Timer('a=s[:]; timsort(a)', setup=setup).repeat(7, 1000))
0.334147930145

Notez que la série d'instructions fait une nouvelle copie des données non triées à chaque passage.

Notez également la technique de chronométrage qui consiste à exécuter la suite de mesures sept fois et à ne conserver que le meilleur temps - cela peut vraiment aider à réduire les distorsions de mesure dues à d'autres processus exécutés sur votre système.

Ce sont mes conseils pour utiliser correctement timeit. J'espère que cela vous aidera :-)

8 votes

Oui, il inclut la copie de la liste (qui est très rapide par rapport au tri lui-même). Si vous ne copiez pas la liste, la première passe trie la liste et les autres passes n'ont pas à faire de travail. Si vous voulez connaître le temps juste pour le tri, exécutez la commande ci-dessus avec et sans l'attribut timsort(a) et prenez la différence :-)

0 votes

Je recommanderais de répéter 7 fois pour chaque configuration, puis de faire la moyenne, plutôt que l'inverse. De cette façon, si chaque pic dû à d'autres processus a une bonne chance d'être entièrement ignoré, plutôt que de faire la moyenne.

89 votes

@max Utiliser le min() plutôt que la moyenne des temps. C'est une recommandation de ma part, de celle de Tim Peters et de celle de Guido van Rossum. Le temps le plus rapide représente la meilleure performance d'un algorithme lorsque les caches sont chargés et que le système n'est pas occupé par d'autres tâches. Tous les temps sont bruyants - le temps le plus rapide est le moins bruyant. Il est facile de montrer que les temps les plus rapides sont les plus reproductibles et donc les plus utiles pour chronométrer deux implémentations différentes.

157voto

Veedrac Points 11712

Je vais vous confier un secret : la meilleure façon d'utiliser timeit est sur la ligne de commande.

Sur la ligne de commande, timeit effectue une analyse statistique correcte : il vous indique la durée de la course la plus courte. C'est une bonne chose car tous l'erreur de temps est positive. C'est donc le temps le plus court qui comporte le moins d'erreur. Il est impossible d'obtenir une erreur négative, car un ordinateur ne peut jamais calculer plus vite qu'il ne peut le faire !

Donc, l'interface de ligne de commande :

%~> python -m timeit "1 + 2"
10000000 loops, best of 3: 0.0468 usec per loop

C'est assez simple, non ?

Vous pouvez mettre des choses en place :

%~> python -m timeit -s "x = range(10000)" "sum(x)"
1000 loops, best of 3: 543 usec per loop

ce qui est également utile !

Si vous voulez plusieurs lignes, vous pouvez soit utiliser la continuation automatique de l'interpréteur de commandes, soit utiliser des arguments séparés :

%~> python -m timeit -s "x = range(10000)" -s "y = range(100)" "sum(x)" "min(y)"
1000 loops, best of 3: 554 usec per loop

Cela donne une configuration de

x = range(1000)
y = range(100)

et les temps

sum(x)
min(y)

Si vous souhaitez disposer de scripts plus longs, vous pourriez être tenté de passer à l'adresse suivante timeit dans un script Python. Je suggère d'éviter cela parce que l'analyse et le timing sont tout simplement meilleurs sur la ligne de commande. Au lieu de cela, j'ai tendance à faire des scripts shell :

 SETUP="

 ... # lots of stuff

 "

 echo Minmod arr1
 python -m timeit -s "$SETUP" "Minmod(arr1)"

 echo pure_minmod arr1
 python -m timeit -s "$SETUP" "pure_minmod(arr1)"

 echo better_minmod arr1
 python -m timeit -s "$SETUP" "better_minmod(arr1)"

 ... etc

Cela peut prendre un peu plus de temps en raison des initialisations multiples, mais normalement ce n'est pas un problème.


Mais si vous quiere à utiliser timeit à l'intérieur de votre module ?

Eh bien, le moyen le plus simple est de le faire :

def function(...):
    ...

timeit.Timer(function).timeit(number=NUMBER)

et cela vous donne un cumulatif ( no minimum !) temps pour exécuter ce nombre de fois.

Pour obtenir une bonne analyse, utilisez .repeat et prendre le minimum :

min(timeit.Timer(function).repeat(repeat=REPEATS, number=NUMBER))

Vous devriez normalement combiner cela avec functools.partial au lieu de lambda: ... pour réduire les frais généraux. Ainsi, vous pourriez avoir quelque chose comme :

from functools import partial

def to_time(items):
    ...

test_items = [1, 2, 3] * 100
times = timeit.Timer(partial(to_time, test_items)).repeat(3, 1000)

# Divide by the number of repeats
time_taken = min(times) / 1000

Vous pouvez aussi le faire :

timeit.timeit("...", setup="from __main__ import ...", number=NUMBER)

ce qui vous donnerait quelque chose de plus proche de la interface à partir de la ligne de commande, mais d'une manière beaucoup moins cool. Le site "from __main__ import ..." vous permet d'utiliser le code de votre module principal à l'intérieur de l'environnement artificiel créé par timeit .

Il est important de noter qu'il s'agit d'une enveloppe pratique pour Timer(...).timeit(...) et n'est donc pas particulièrement bon en matière de timing. Personnellement, je préfère de loin utiliser Timer(...).repeat(...) comme je l'ai montré ci-dessus.


Avertissements

Il y a quelques réserves à propos de timeit qui s'appliquent partout.

  • Les frais généraux ne sont pas comptabilisés. Disons que vous voulez chronométrer x += 1 pour savoir combien de temps prend une addition :

    >>> python -m timeit -s "x = 0" "x += 1"
    10000000 loops, best of 3: 0.0476 usec per loop

    Eh bien, c'est no 0,0476 µs. Vous savez seulement que c'est moins que cela. Toute erreur est positive.

    Alors essayez de trouver pur tête :

    >>> python -m timeit -s "x = 0" ""      
    100000000 loops, best of 3: 0.014 usec per loop

    C'est une bonne 30% juste à cause du timing ! Cela peut massivement fausser les timings relatifs. Mais vous ne vous souciez vraiment que de la en ajoutant les temps de recherche pour les x doivent également être inclus dans les frais généraux :

    >>> python -m timeit -s "x = 0" "x"
    100000000 loops, best of 3: 0.0166 usec per loop

    La différence n'est pas beaucoup plus grande, mais elle est là.

  • Les méthodes mutantes sont dangereuses.

    >>> python -m timeit -s "x = [0]*100000" "while x: x.pop()"
    10000000 loops, best of 3: 0.0436 usec per loop

    Mais c'est complètement faux ! x est la liste vide après la première itération. Vous devrez réinitialiser :

    >>> python -m timeit "x = [0]*100000" "while x: x.pop()"
    100 loops, best of 3: 9.79 msec per loop

    Mais alors vous avez beaucoup de frais généraux. Il faut les comptabiliser séparément.

    >>> python -m timeit "x = [0]*100000"                   
    1000 loops, best of 3: 261 usec per loop

    Notez que la soustraction des frais généraux est raisonnable ici. seulement parce que les frais généraux ne représentent qu'une petite fraction du temps.

    Pour votre exemple, il convient de noter que ambos Insertion Sort et Tim Sort ont complètement inhabituel comportements de temporisation pour les listes déjà triées. Cela signifie que vous aurez besoin d'un random.shuffle entre les sortes si vous voulez éviter de bousiller vos timings.

2 votes

Que signifie usec ? est-ce que c'est des microsecondes ?

3 votes

@HasanIqbalAnik Oui.

0 votes

@StefanPochmann Parce qu'il n'essaie pas d'échantillonner plusieurs fois.

114voto

zzart Points 2162

Si vous voulez comparer deux blocs de code / fonctions rapidement, vous pouvez le faire :

import timeit

start_time = timeit.default_timer()
func1()
print(timeit.default_timer() - start_time)

start_time = timeit.default_timer()
func2()
print(timeit.default_timer() - start_time)

48voto

unutbu Points 222216

Je trouve que la façon la plus simple d'utiliser timeit est de le faire à partir de la ligne de commande :

Étant donné que test.py :

def InsertionSort(): ...
def TimSort(): ...

exécutez timeit comme ceci :

% python -mtimeit -s'import test' 'test.InsertionSort()'
% python -mtimeit -s'import test' 'test.TimSort()'

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X