¿Cómo puedo cronometrar un segmento de código para probar el rendimiento con Pythons timeit?

162

Tengo un script de Python que funciona como debería, pero necesito escribir el tiempo de ejecución. Busqué en Google que debería usar, timeitpero parece que no puedo hacer que funcione.

Mi script de Python se ve así:

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)

Lo que necesito es el tiempo que lleva ejecutar la consulta y escribirla en el archivo results_update.txt. El propósito es probar una declaración de actualización para mi base de datos con diferentes índices y mecanismos de ajuste.

Mestika
fuente
¿Fue / es su pregunta específica timeit? Supongo que no. En ese caso, probablemente debería eliminar "with Pythons timeit" del título.
Martin Thoma

Respuestas:

275

Puede usar time.time()o time.clock()antes y después del bloque que desea cronometrar.

import time

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

total = t1-t0

Este método no es tan exacto como timeit(no promedia varias ejecuciones) pero es sencillo.

time.time()(en Windows y Linux) y time.clock()(en Linux) no son lo suficientemente precisos para funciones rápidas (obtienes total = 0). En este caso o si desea promediar el tiempo transcurrido por varias ejecuciones, debe llamar manualmente a la función varias veces (como creo que ya lo hace en su código de ejemplo y el tiempo lo hace automáticamente cuando establece su argumento de número )

import time

def myfast():
   code

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

total_n = t1-t0

En Windows, como Corey declaró en el comentario, time.clock()tiene una precisión mucho mayor (microsegundos en lugar de segundos) y se prefiere sobre time.time().

joaquin
fuente
8
Para su información en Windows, use time.clock () en lugar de time.time ()
Corey Goldberg
44
Gracias Corey, ¿por qué? porque el reloj es más preciso (microsegundos) o hay algo más?
joaquin
11
Puede usar timeit.default_timer () para hacer que su plataforma de código sea independiente; devuelve time.clock () o time.time () según corresponda para el sistema operativo.
Marc Stober
66
En lugar de seleccionar un reloj a mano, use timeit.default_timer; Python ya ha hecho el trabajo por ti. Pero realmente, debe usar en timeit.timeit(myfast, number=n)lugar de reinventar la rueda de llamadas repetitivas (y perder el hecho de que timeitdeshabilita el recolector de basura mientras ejecuta el código repetidamente).
Martijn Pieters
15
actualización: time.clock () ahora está en desuso. Ahora debe usar time.time (). En realidad, desde la versión 3.3, la mejor opción sería time.perf_counter ()
Madlozoz
42

Si está perfilando su código y puede usar IPython, tiene la función mágica %timeit.

%%timeit opera en celdas.

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
fuente
36

Aparte del tiempo, este código que muestra es simplemente incorrecto: ejecuta 100 conexiones (ignorando completamente todas menos la última), y luego, cuando realiza la primera llamada de ejecución, le pasa una variable local query_stmtque solo inicializa después la ejecución llamada.

Primero, corrija su código, sin preocuparse aún por el tiempo: es decir, una función que realiza o recibe una conexión y realiza 100 o 500 o cualquier número de actualizaciones en esa conexión, luego cierra la conexión. Una vez que tenga su código funcionando correctamente, es el punto correcto para pensar en usartimeit !

Específicamente, si la función que desea cronometrarfoobar se llama sin parámetros, puede usar timeit.timeit (2.6 o posterior; es más complicado en 2.5 y antes):

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

Será mejor que especifique el número de ejecuciones porque el valor predeterminado, un millón, puede ser alto para su caso de uso (lo que lleva a pasar mucho tiempo en este código ;-).

Alex Martelli
fuente
26
Después de luchar con esto durante los últimos minutos, quiero que los futuros espectadores sepan que probablemente también desee pasar una variable de configuración si su función foobarestá en un archivo principal. Así: timeit.timeit('foobar()','from __main__ import foobar',number=1000)
Rich
3
En Python 2.7.8, simplemente podría usartimeit.timeit( foobar, number=1000 )
9

Concéntrese en una cosa específica . La E / S de disco es lenta, por lo que sacaría eso de la prueba si todo lo que va a modificar es la consulta de la base de datos.

Y si necesita cronometrar la ejecución de su base de datos, busque las herramientas de la base de datos, como solicitar el plan de consulta, y tenga en cuenta que el rendimiento varía no solo con la consulta exacta y qué índices tiene, sino también con la carga de datos (cuántos datos has almacenado)

Dicho esto, simplemente puede poner su código en una función y ejecutar esa función con timeit.timeit():

def function_to_repeat():
    # ...

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

Esto deshabilitaría la recolección de basura, llamaría repetidamente a la function_to_repeat()función y mediría la duración total de esas llamadas usandotimeit.default_timer() , que es el reloj disponible más preciso para su plataforma específica.

Debe mover el código de configuración fuera de la función repetida; por ejemplo, primero debe conectarse a la base de datos y luego solo las consultas. Use el setupargumento para importar o crear esas dependencias y pasarlas a su función:

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

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

tomaría los globales function_to_repeat, var1y var2de su script y pasaría a la función cada repetición.

Martijn Pieters
fuente
Poner el código en una función es un paso que estaba buscando, ya que simplemente convertir el código en una cadena e evaling no va a volar para nada que no sea completamente trivial. thx
javadba
2

Veo que la pregunta ya ha sido respondida, pero todavía quiero agregar mis 2 centavos por lo mismo.

También me he enfrentado a un escenario similar en el que tengo que probar los tiempos de ejecución para varios enfoques y, por lo tanto, escribí un pequeño script, que llama a timeit en todas las funciones escritas en él.

El script también está disponible como github gist aquí .

Espero que te ayude a ti y a los demás.

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
fuente
2

Aquí hay un contenedor simple para la respuesta de Steven. Esta función no realiza repeticiones / promedios, solo le ahorra tener que repetir el código de sincronización en todas partes :)

'''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))
intercambio de información
fuente
0

El conjunto de pruebas no intenta utilizar lo importado, timeitpor lo que es difícil saber cuál fue la intención. Sin embargo, esta es una respuesta canónica, por lo que un ejemplo completo timeitparece estar en orden, elaborando la respuesta de Martijn .

Los documentostimeit ofrecen muchos ejemplos y banderas que vale la pena ver. El uso básico en la línea de comando es:

$ 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

Ejecute con -hpara ver todas las opciones. Python MOTW tiene una gran sección timeitque muestra cómo ejecutar módulos a través de cadenas de código de importación y multilínea desde la línea de comandos.

En forma de script, normalmente lo uso así:

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

Puede colocar fácilmente las funciones y argumentos que necesita. Tenga precaución cuando use funciones impuras y cuide el estado.

Salida de muestra:

$ 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
fuente
0

Otro ejemplo 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
fuente