Comment chronométrer un segment de code pour tester les performances avec Pythons timeit?

162

J'ai un script python qui fonctionne comme il se doit, mais j'ai besoin d'écrire l'heure d'exécution. J'ai cherché sur Google que je devrais utiliser timeitmais je n'arrive pas à le faire fonctionner.

Mon script Python ressemble à ceci:

import sys
import getopt
import timeit
import random
import os
import re
import ibm_db
import time
from string import maketrans
myfile = open("results_update.txt", "a")

for r in range(100):
    rannumber = random.randint(0, 100)

    update = "update TABLE set val = %i where MyCount >= '2010' and MyCount < '2012' and number = '250'" % rannumber
    #print rannumber

    conn = ibm_db.pconnect("dsn=myDB","usrname","secretPWD")

for r in range(5):
    print "Run %s\n" % r        
    ibm_db.execute(query_stmt)
 query_stmt = ibm_db.prepare(conn, update)

myfile.close()
ibm_db.close(conn)

Ce dont j'ai besoin, c'est du temps nécessaire pour exécuter la requête et l'écrire dans le fichier results_update.txt. Le but est de tester une instruction de mise à jour pour ma base de données avec différents index et mécanismes de réglage.

Mestika
la source
Votre question était / est-elle spécifique timeit? Je suppose que non. Dans ce cas, vous devriez probablement supprimer "with Pythons timeit" du titre.
Martin Thoma

Réponses:

275

Vous pouvez utiliser time.time()ou time.clock()avant et après le bloc que vous souhaitez chronométrer.

import time

t0 = time.time()
code_block
t1 = time.time()

total = t1-t0

Cette méthode n'est pas aussi exacte que timeit(elle ne fait pas la moyenne de plusieurs passages) mais elle est simple.

time.time()(sous Windows et Linux) et time.clock()(sous Linux) ne sont pas assez précis pour les fonctions rapides (vous obtenez total = 0). Dans ce cas ou si vous voulez faire la moyenne du temps écoulé par plusieurs exécutions, vous devez appeler manuellement la fonction plusieurs fois (comme je pense que vous le faites déjà dans votre exemple de code et timeit le fait automatiquement lorsque vous définissez son argument numérique )

import time

def myfast():
   code

n = 10000
t0 = time.time()
for i in range(n): myfast()
t1 = time.time()

total_n = t1-t0

Dans Windows, comme Corey l'a déclaré dans le commentaire, time.clock()a une précision beaucoup plus élevée (microseconde au lieu de seconde) et est préférable à time.time().

Joaquin
la source
8
fyi sous windows, utilisez time.clock () au lieu de time.time ()
Corey Goldberg
4
Merci Corey, pourquoi? parce que l'horloge est plus précise (microsecondes) ou il y a quelque chose de plus?
joaquin
11
Vous pouvez utiliser timeit.default_timer () pour rendre votre plate-forme de code indépendante; il renvoie time.clock () ou time.time () selon le système d'exploitation.
Marc Stober
6
Plutôt que de sélectionner une horloge à la main, utilisez timeit.default_timer; Python a déjà fait le travail pour vous. Mais vraiment, vous devriez utiliser timeit.timeit(myfast, number=n)au lieu de réinventer la roue des appels répétitifs (et manquer le fait qui timeitdésactive le ramasse-miettes lors de l'exécution répétée du code).
Martijn Pieters
15
update: time.clock () est désormais obsolète. Vous devez maintenant utiliser time.time (). En fait, depuis la version 3.3, la meilleure option serait time.perf_counter ()
Madlozoz
42

Si vous profilez votre code et pouvez utiliser IPython, il a la fonction magique %timeit.

%%timeit fonctionne sur les cellules.

In [2]: %timeit cos(3.14)
10000000 loops, best of 3: 160 ns per loop

In [3]: %%timeit
   ...: cos(3.14)
   ...: x = 2 + 3
   ...: 
10000000 loops, best of 3: 196 ns per loop
munk
la source
36

Indépendamment du timing, ce code que vous affichez est tout simplement incorrect: vous exécutez 100 connexions (en ignorant complètement tout sauf la dernière), puis lorsque vous effectuez le premier appel d'exécution, vous lui transmettez une variable locale query_stmtque vous n'initialisez qu'après l'exécution. appel.

Tout d'abord, corrigez votre code, sans vous soucier encore du timing: c'est-à-dire une fonction qui établit ou reçoit une connexion et effectue 100 ou 500 ou n'importe quel nombre de mises à jour sur cette connexion, puis ferme la connexion. Une fois que votre code fonctionne correctement, c'est le bon moment pour penser à l'utiliser timeit!

Plus précisément, si la fonction que vous souhaitez chronométrer est une fonction appelée sans paramètre, foobarvous pouvez utiliser timeit.timeit (2.6 ou version ultérieure - c'est plus compliqué en 2.5 et avant):

timeit.timeit('foobar()', number=1000)

Vous feriez mieux de spécifier le nombre d'exécutions car la valeur par défaut, un million, peut être élevée pour votre cas d'utilisation (ce qui conduit à passer beaucoup de temps dans ce code ;-).

Alex Martelli
la source
26
Après avoir lutté avec cela pendant les dernières minutes, je tiens à informer les futurs téléspectateurs que vous souhaitez probablement également transmettre une variable de configuration si votre fonction foobarest dans un fichier principal. Comme ça: timeit.timeit('foobar()','from __main__ import foobar',number=1000)
Rich
3
Dans Python 2.7.8, vous pouvez simplement utilisertimeit.timeit( foobar, number=1000 )
9

Concentrez-vous sur une chose spécifique . Les E / S de disque sont lentes, je retirerais donc cela du test si tout ce que vous voulez modifier est la requête de base de données.

Et si vous avez besoin de chronométrer l'exécution de votre base de données, recherchez plutôt des outils de base de données, comme demander le plan de requête, et notez que les performances varient non seulement en fonction de la requête exacte et des index dont vous disposez, mais également en fonction de la charge de données (combien de données vous avez stocké).

Cela dit, vous pouvez simplement mettre votre code dans une fonction et exécuter cette fonction avec timeit.timeit():

def function_to_repeat():
    # ...

duration = timeit.timeit(function_to_repeat, number=1000)

Cela désactiverait le garbage collection, appellerait à plusieurs reprises la function_to_repeat()fonction et chronométrerait la durée totale de ces appels en utilisant timeit.default_timer(), qui est l'horloge disponible la plus précise pour votre plate-forme spécifique.

Vous devez déplacer le code de configuration hors de la fonction répétée; par exemple, vous devez d'abord vous connecter à la base de données, puis chronométrer uniquement les requêtes. Utilisez l' setupargument pour importer ou créer ces dépendances, puis passez-les dans votre fonction:

def function_to_repeat(var1, var2):
    # ...

duration = timeit.timeit(
    'function_to_repeat(var1, var2)',
    'from __main__ import function_to_repeat, var1, var2', 
    number=1000)

saisirait les globaux function_to_repeat, var1et à var2partir de votre script et les passerait à la fonction chaque répétition.

Martijn Pieters
la source
Mettre le code dans une fonction est une étape que je recherchais - puisque le simple fait de faire du code une chaîne de caractères et d'ingérer evalne va pas voler pour quelque chose qui n'est pas complètement banal. thx
javadba
2

Je vois que la question a déjà été répondue, mais je veux toujours ajouter mes 2 cents pour la même chose.

J'ai également fait face à un scénario similaire dans lequel je dois tester les temps d'exécution pour plusieurs approches et donc écrire un petit script, qui appelle timeit sur toutes les fonctions qui y sont écrites.

Le script est également disponible en tant que github gist ici .

J'espère que cela vous aidera, vous et les autres.

from random import random
import types

def list_without_comprehension():
    l = []
    for i in xrange(1000):
        l.append(int(random()*100 % 100))
    return l

def list_with_comprehension():
    # 1K random numbers between 0 to 100
    l = [int(random()*100 % 100) for _ in xrange(1000)]
    return l


# operations on list_without_comprehension
def sort_list_without_comprehension():
    list_without_comprehension().sort()

def reverse_sort_list_without_comprehension():
    list_without_comprehension().sort(reverse=True)

def sorted_list_without_comprehension():
    sorted(list_without_comprehension())


# operations on list_with_comprehension
def sort_list_with_comprehension():
    list_with_comprehension().sort()

def reverse_sort_list_with_comprehension():
    list_with_comprehension().sort(reverse=True)

def sorted_list_with_comprehension():
    sorted(list_with_comprehension())


def main():
    objs = globals()
    funcs = []
    f = open("timeit_demo.sh", "w+")

    for objname in objs:
        if objname != 'main' and type(objs[objname]) == types.FunctionType:
            funcs.append(objname)
    funcs.sort()
    for func in funcs:
        f.write('''echo "Timing: %(funcname)s"
python -m timeit "import timeit_demo; timeit_demo.%(funcname)s();"\n\n
echo "------------------------------------------------------------"
''' % dict(
                funcname = func,
                )
            )

    f.close()

if __name__ == "__main__":
    main()

    from os import system

    #Works only for *nix platforms
    system("/bin/bash timeit_demo.sh")

    #un-comment below for windows
    #system("cmd timeit_demo.sh")
Abhijit Mamarde
la source
2

Voici un emballage simple pour la réponse de Steven. Cette fonction ne fait pas d'exécutions / moyennes répétées, vous évite simplement d'avoir à répéter le code de synchronisation partout :)

'''function which prints the wall time it takes to execute the given command'''
def time_func(func, *args): #*args can take 0 or more 
  import time
  start_time = time.time()
  func(*args)
  end_time = time.time()
  print("it took this long to run: {}".format(end_time-start_time))
information_interchange
la source
0

La suite de tests ne tente pas d'utiliser le fichier importé timeit, il est donc difficile de dire quelle était l'intention. Néanmoins, c'est une réponse canonique, donc un exemple complet de timeitsemble en ordre, élaborant sur la réponse de Martijn .

Les documents pourtimeit offrent de nombreux exemples et indicateurs à vérifier. L'utilisation de base sur la ligne de commande est:

$ python -mtimeit "all(True for _ in range(1000))"
2000 loops, best of 5: 161 usec per loop
$ python -mtimeit "all([True for _ in range(1000)])"
2000 loops, best of 5: 116 usec per loop

Exécutez avec -hpour voir toutes les options. Python MOTW a une grande section sur timeitqui montre comment exécuter des modules via l'importation et des chaînes de code multilignes à partir de la ligne de commande.

Sous forme de script, je l'utilise généralement comme ceci:

import argparse
import copy
import dis
import inspect
import random
import sys
import timeit

def test_slice(L):
    L[:]

def test_copy(L):
    L.copy()

def test_deepcopy(L):
    copy.deepcopy(L)

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--n", type=int, default=10 ** 5)
    parser.add_argument("--trials", type=int, default=100)
    parser.add_argument("--dis", action="store_true")
    args = parser.parse_args()
    n = args.n
    trials = args.trials
    namespace = dict(L = random.sample(range(n), k=n))
    funcs_to_test = [x for x in locals().values() 
                     if callable(x) and x.__module__ == __name__]
    print(f"{'-' * 30}\nn = {n}, {trials} trials\n{'-' * 30}\n")

    for func in funcs_to_test:
        fname = func.__name__
        fargs = ", ".join(inspect.signature(func).parameters)
        stmt = f"{fname}({fargs})"
        setup = f"from __main__ import {fname}"
        time = timeit.timeit(stmt, setup, number=trials, globals=namespace)
        print(inspect.getsource(globals().get(fname)))

        if args.dis:
            dis.dis(globals().get(fname))

        print(f"time (s) => {time}\n{'-' * 30}\n")

Vous pouvez facilement supprimer les fonctions et les arguments dont vous avez besoin. Soyez prudent lorsque vous utilisez des fonctions impures et faites attention à l'état.

Exemple de sortie:

$ python benchmark.py --n 10000
------------------------------
n = 10000, 100 trials
------------------------------

def test_slice(L):
    L[:]

time (s) => 0.015502399999999972
------------------------------

def test_copy(L):
    L.copy()

time (s) => 0.01651419999999998
------------------------------

def test_deepcopy(L):
    copy.deepcopy(L)

time (s) => 2.136012
------------------------------
Ggorlen
la source
0

Un autre exemple simple de timeit:

def your_function_to_test():
   # do some stuff...

time_to_run_100_times = timeit.timeit(lambda: your_function_to_test, number=100)
sam
la source