Comment utiliser le module timeit

351

Je comprends le concept de ce qui timeitfait mais je ne sais pas comment l'implémenter dans mon code.

Comment comparer deux fonctions, disons insertion_sortet tim_sort, avec timeit?

Neemaximo
la source

Réponses:

266

La façon dont timeit fonctionne consiste à exécuter une fois le code d'installation, puis à effectuer des appels répétés à une série d'instructions. Donc, si vous voulez tester le tri, une certaine prudence est requise afin qu'un passage à un tri sur place n'affecte pas le prochain passage avec des données déjà triées (ce qui, bien sûr, ferait vraiment briller le Timsort car il fonctionne mieux lorsque les données sont déjà partiellement commandées).

Voici un exemple de configuration 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 crée une nouvelle copie des données non triées à chaque passage.

Notez également la technique de synchronisation consistant à exécuter la suite de mesures sept fois et à ne conserver que le meilleur moment - cela peut vraiment aider à réduire les distorsions de mesure dues à d'autres processus en cours d'exécution sur votre système.

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

Raymond Hettinger
la source
8
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 cependant, la première passe trie la liste et les autres passes n'ont pas à faire de travail. Si vous voulez connaître l'heure juste pour le tri, alors exécutez ce qui précède avec et sans le timsort(a)et prenez la différence :-)
Raymond Hettinger
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 de bonnes chances d'être complètement ignoré, plutôt que d'être moyenné.
max
75
@max Utilisez le min () plutôt que la moyenne des timings. C'est une recommandation de ma part, de Tim Peters et de Guido van Rossum. Le temps le plus rapide représente le meilleur qu'un algorithme puisse effectuer lorsque les caches sont chargés et que le système n'est pas occupé par d'autres tâches. Tous les horaires sont bruyants - le temps le plus rapide est le moins bruyant. Il est facile de montrer que les synchronisations les plus rapides sont les plus reproductibles et donc les plus utiles lors de la synchronisation de deux implémentations différentes.
Raymond Hettinger
4
Vous calculez une moyenne (enfin, totale, mais c'est équivalent) pour 1000 entrées; puis répétez 7 fois, et prenez le minimum . Vous avez besoin de la moyenne de plus de 1000 entrées, car vous voulez la complexité moyenne de l'algorithme (pas le meilleur des cas). Vous avez besoin du minimum précisément pour la raison que vous avez donnée. Je pensais pouvoir améliorer votre approche en choisissant une entrée, en exécutant l'algorithme 7 fois, en prenant le minimum; puis en le répétant pour 1000 entrées différentes, et en prenant la moyenne. Ce que je ne savais pas, c'est que vous le faites .repeat(7,1000)déjà (en utilisant la même graine)! Votre solution est donc parfaite IMO.
max
5
Je peux seulement ajouter que la façon dont vous allouez votre budget de 7 000 exécutions (par exemple, .repeat(7, 1000)vs .repeat(2, 3500)vs .repeat(35, 200) devrait dépendre de la façon dont l'erreur due à la charge du système se compare à l'erreur due à la variabilité des entrées. Dans le cas extrême, si votre système est toujours sous forte charge et que vous voyez une longue queue mince à gauche de la distribution du temps d'exécution (lorsque vous l'attrapez dans un état inactif rare), vous pourriez même trouver .repeat(7000,1)plus utile que .repeat(7,1000)si vous ne peut pas budgéter plus de 7 000 exécutions.
max
277

Si vous souhaitez utiliser timeitdans une session Python interactive, il existe deux options pratiques:

  1. Utilisez le shell IPython . Il dispose de la %timeitfonction spéciale pratique :

    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 autres noms que vous avez définis plus tôt au cours de la session interactive en les important __main__dans l'instruction setup:

    >>> 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]
Sven Marnach
la source
97
+1 pour montrer la from __main__ import ftechnique. Je ne pense pas que ce soit aussi largement connu qu'il devrait l'être. Il est utile dans des cas comme celui-ci où un appel de fonction ou de méthode est synchronisé. Dans d'autres cas (chronométrage d'une série d'étapes), cela est moins utile car il introduit une surcharge d'appel de fonction.
Raymond Hettinger
15
Vous pouvez simplement faire%timeit f(x)
qed
Remarque: la configuration "import f" permet d'accéder à fa en lecture locale rapide - qui ne reflète pas exactement un appel de fonction globale (de fonction rapide courte) dans un code normal typique. Dans Py3.5 + de vrais globaux peuvent être fournis: "Modifié dans la version 3.5: Le paramètre facultatif des globaux a été ajouté."; Avant les globales du module timeit où c'était inévitable (ce qui n'a pas beaucoup de sens). Il se peut que les globales du code appelant ( sys._getframe(N).f_globals) aient été par défaut depuis le début.
kxr
140

Je vais vous révéler un secret: la meilleure façon de l'utiliser timeitest en ligne de commande.

Sur la ligne de commande, timeitfait une analyse statistique appropriée: il vous indique combien de temps a duré le plus court. C'est bien parce que toute erreur de synchronisation est positive. Ainsi, le temps le plus court contient le moins d'erreurs. Il n'y a aucun moyen d'obtenir une erreur négative car un ordinateur ne peut jamais calculer plus vite qu'il ne peut le faire!

Ainsi, 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, hein?

Vous pouvez configurer des choses:

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

ce qui est utile aussi!

Si vous voulez plusieurs lignes, vous pouvez utiliser la continuation automatique du shell ou 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 fois

sum(x)
min(y)

Si vous souhaitez avoir des scripts plus longs, vous pourriez être tenté de passer à l' timeitintérieur d'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 multiples initialisations, mais normalement ce n'est pas un gros problème.


Mais que faire si vous souhaitez utiliser l' timeitintérieur de votre module?

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

def function(...):
    ...

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

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

Pour obtenir une bonne analyse, utilisez .repeatet prenez le minimum:

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

Vous devez normalement combiner cela avec functools.partialau 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 également faire:

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

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

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


Avertissements

Il y a quelques mises en garde timeitqui tiennent partout.

  • Les frais généraux ne sont pas pris en compte. Dites que vous voulez chronométrer x += 1, pour savoir combien de temps prend l'addition:

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

    Eh bien, ce n'est pas 0,0476 µs. Vous savez seulement que c'est moins que ça. Toute erreur est positive.

    Essayez donc de trouver des frais généraux purs :

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

    C'est un bon 30% de frais généraux rien que du timing! Cela peut fausser considérablement les synchronisations relatives. Mais vous ne vous souciez que de l' ajout de timings; les horaires de recherche xdoivent é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 de mutation 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! xest 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. Tenez-en compte séparément.

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

    Notez que la soustraction de la surcharge n'est raisonnable ici que parce que la surcharge est une petite fraction du temps.

    Pour votre exemple, il convient de noter que le tri par insertion et le tri temporel ont tous deux des comportements temporels inhabituels pour les listes déjà triées. Cela signifie que vous aurez besoin d'un random.shuffletri entre si vous voulez éviter de perturber vos horaires.

Veedrac
la source
1
que signifie usec? est-ce en microsecondes?
Hasan Iqbal
2
@HasanIqbalAnik Oui.
Veedrac
@StefanPochmann Parce qu'il n'essaie pas d'échantillonner plusieurs fois.
Veedrac
Les lecteurs de cette réponse peuvent également être intéressés par Utiliser Python à timeitpartir d'un programme mais fonctionnant de la même manière que la ligne de commande? .
Graham
@Veedrac Compte tenu de votre déclaration sur la soustraction de la surcharge de synchronisation pure, timeitexécute une passinstruction lorsque aucun argument n'est donné, ce qui, bien sûr, prend un certain temps. Si des arguments sont fournis, passils ne seront pas exécutés, donc soustraire certains 0.014usecs de chaque synchronisation serait incorrect.
Arne
99

Si vous souhaitez comparer rapidement deux blocs de code / fonctions, 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)
zzart
la source
43

Je trouve que le moyen le plus simple d'utiliser timeit est à partir de la ligne de commande:

Étant donné 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()'
unutbu
la source
18

pour moi, c'est le moyen le plus rapide:

import timeit
def foo():
    print("here is my code to time...")


timeit.timeit(stmt=foo, number=1234567)
Rodrigo Laguna
la source
12
# Генерация целых чисел

def gen_prime(x):
    multiples = []
    results = []
    for i in range(2, x+1):
        if i not in multiples:
            results.append(i)
            for j in range(i*i, x+1, i):
                multiples.append(j)

    return results


import timeit

# Засекаем время

start_time = timeit.default_timer()
gen_prime(3000)
print(timeit.default_timer() - start_time)

# start_time = timeit.default_timer()
# gen_prime(1001)
# print(timeit.default_timer() - start_time)
David Webb
la source
7

Cela fonctionne très bien:

  python -m timeit -c "$(cat file_name.py)"
Ohad Rubin
la source
Quel serait l'équivalent de Windows?
Shailen
2
Comment passez-vous les paramètres, si le script en requiert?
Juuso Ohtonen
3

permet de configurer le même dictionnaire dans chacun des éléments suivants et de tester le temps d'exécution.

L'argument de configuration consiste essentiellement à configurer le dictionnaire

Le numéro est d'exécuter le code 1000000 fois. Pas la configuration mais le stmt

Lorsque vous exécutez cela, vous pouvez voir que l'index est bien plus rapide que get. Vous pouvez l'exécuter plusieurs fois pour voir.

Le code essaie essentiellement d'obtenir la valeur de c dans le dictionnaire.

import timeit

print('Getting value of C by index:', timeit.timeit(stmt="mydict['c']", setup="mydict={'a':5, 'b':6, 'c':7}", number=1000000))
print('Getting value of C by get:', timeit.timeit(stmt="mydict.get('c')", setup="mydict={'a':5, 'b':6, 'c':7}", number=1000000))

Voici mes résultats, les vôtres seront différents.

par indice: 0.20900007452246427

par get: 0.54841166886888

Stryker
la source
Quelle version de python utilisez-vous?
Eduardo
3

passez simplement votre code entier comme argument de timeit:

import timeit

print(timeit.timeit(

"""   
limit = 10000
prime_list = [i for i in range(2, limit+1)]

for prime in prime_list:
    for elem in range(prime*2, max(prime_list)+1, prime):
        if elem in prime_list:
            prime_list.remove(elem)
"""   
, number=10))
Sébastien Wieckowski
la source
1
import timeit


def oct(x):
   return x*x


timeit.Timer("for x in range(100): oct(x)", "gc.enable()").timeit()
Yagmur SAHIN
la source
Qu'est-ce que c'est gc.enable()?
Robin Andrews
0

Le module timeit intégré fonctionne mieux à partir de la ligne de commande IPython.

Pour chronométrer des fonctions à partir d'un module:

from timeit import default_timer as timer
import sys

def timefunc(func, *args, **kwargs):
    """Time a function. 

    args:
        iterations=3

    Usage example:
        timeit(myfunc, 1, b=2)
    """
    try:
        iterations = kwargs.pop('iterations')
    except KeyError:
        iterations = 3
    elapsed = sys.maxsize
    for _ in range(iterations):
        start = timer()
        result = func(*args, **kwargs)
        elapsed = min(timer() - start, elapsed)
    print(('Best of {} {}(): {:.9f}'.format(iterations, func.__name__, elapsed)))
    return result
ChaimG
la source
0

Exemple d'utilisation de l'interpréteur Python REPL avec une fonction qui accepte des paramètres.

>>> import timeit                                                                                         

>>> def naive_func(x):                                                                                    
...     a = 0                                                                                             
...     for i in range(a):                                                                                
...         a += i                                                                                        
...     return a                                                                                          

>>> def wrapper(func, *args, **kwargs):                                                                   
...     def wrapper():                                                                                    
...         return func(*args, **kwargs)                                                                  
...     return wrapper                                                                                    

>>> wrapped = wrapper(naive_func, 1_000)                                                                  

>>> timeit.timeit(wrapped, number=1_000_000)                                                              
0.4458435332577161                                                                                        
Vlad Bezden
la source
0

Vous devez créer deux fonctions, puis exécuter quelque chose de similaire à ceci. Remarquez que vous souhaitez choisir le même nombre d'exécutions / exécutions pour comparer pomme à pomme.
Cela a été testé sous Python 3.7.

entrez la description de l'image ici Voici le code pour le copier facilement

!/usr/local/bin/python3
import timeit

def fibonacci(n):
    """
    Returns the n-th Fibonacci number.
    """
    if(n == 0):
        result = 0
    elif(n == 1):
        result = 1
    else:
        result = fibonacci(n-1) + fibonacci(n-2)
    return result

if __name__ == '__main__':
    import timeit
    t1 = timeit.Timer("fibonacci(13)", "from __main__ import fibonacci")
    print("fibonacci ran:",t1.timeit(number=1000), "milliseconds")
grepit
la source