Como cronometrar um segmento de código para testar o desempenho com o Pythons timeit?

162

Eu tenho um script python que funciona exatamente como deveria, mas preciso escrever o tempo de execução. Pesquisei no Google que deveria usar, timeitmas não consigo fazê-lo funcionar.

Meu script Python fica assim:

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)

O que eu preciso é o tempo que leva para executar a consulta e gravá-la no arquivo results_update.txt. O objetivo é testar uma instrução de atualização para meu banco de dados com diferentes índices e mecanismos de ajuste.

Mestika
fonte
Sua pergunta foi específica sobre / timeit? Eu acho que não. Nesse caso, você provavelmente deve remover "with Pythons timeit" do título.
Martin Thoma

Respostas:

275

Você pode usar time.time()ou time.clock()antes e depois do bloco que deseja cronometrar.

import time

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

total = t1-t0

Esse método não é tão exato quanto timeit(não mede várias execuções), mas é direto.

time.time()(no Windows e Linux) e time.clock()(no Linux) não são precisos o suficiente para funções rápidas (você obtém total = 0). Nesse caso, ou se você deseja calcular a média do tempo decorrido por várias execuções, é necessário chamar manualmente a função várias vezes (como eu acho que você já faz no exemplo de código e tempo, isso acontece automaticamente quando você define seu argumento numérico )

import time

def myfast():
   code

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

total_n = t1-t0

No Windows, como Corey afirmou no comentário, time.clock()tem uma precisão muito maior (microssegundo em vez de segundo) e é preferível time.time().

joaquin
fonte
8
fyi no windows, use time.clock () em vez de time.time ()
Corey Goldberg
4
Obrigado Corey, por quê? porque o relógio é mais preciso (microssegundos) ou há algo mais?
Joaquin
11
Você pode usar timeit.default_timer () para tornar sua plataforma de código independente; retorna time.clock () ou time.time () conforme apropriado para o sistema operacional.
Marc Stober
6
Em vez de selecionar um relógio manualmente, use timeit.default_timer; O Python já fez o trabalho para você. Mas, na verdade, você deve usar em timeit.timeit(myfast, number=n)vez de reinventar a roda de chamada repetitiva (e perder o fato de que timeitdesativa o coletor de lixo enquanto executa o código repetidamente).
Martijn Pieters
15
update: time.clock () agora está obsoleto. Agora você deve usar time.time (). Na verdade, desde a versão 3.3, a melhor opção seria time.perf_counter ()
Madlozoz
42

Se você cria um perfil do seu código e pode usar o IPython, ele tem a função mágica %timeit.

%%timeit opera em células.

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

Independentemente do tempo, esse código que você mostra é simplesmente incorreto: você executa 100 conexões (ignorando completamente todas, exceto a última) e, em seguida, quando você faz a primeira chamada de execução, passa uma variável local query_stmtque somente inicializa após a execução ligar.

Primeiro, corrija seu código, sem se preocupar com o tempo ainda: ou seja, uma função que faz ou recebe uma conexão e executa 100 ou 500 ou qualquer número de atualizações nessa conexão e, em seguida, fecha a conexão. Depois de ter seu código funcionando corretamente, é o ponto correto para pensar em usá timeit-lo!

Especificamente, se a função que você deseja cronometrar for uma chamada sem parâmetro, foobarvocê pode usar timeit.timeit (2.6 ou posterior - é mais complicado no 2.5 e anterior):

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

É melhor especificar o número de execuções, porque o padrão, um milhão, pode ser alto para o seu caso de uso (levando a gastar muito tempo nesse código ;-).

Alex Martelli
fonte
26
Depois de lutar com isso nos últimos minutos, quero que os futuros visualizadores saibam que você provavelmente também deseja passar uma variável de configuração se sua função foobarestiver em um arquivo principal. Assim: timeit.timeit('foobar()','from __main__ import foobar',number=1000)
Rich
3
Em Python 2.7.8, você poderia simplesmente usartimeit.timeit( foobar, number=1000 )
9

Concentre-se em uma coisa específica . A E / S do disco é lenta, portanto, eu a retiraria do teste se tudo o que você quiser ajustar é a consulta ao banco de dados.

E se você precisar cronometrar a execução do banco de dados, procure por ferramentas de banco de dados, como solicitar o plano de consulta e observe que o desempenho varia não apenas com a consulta exata e quais índices você possui, mas também com a carga de dados (quantos dados você armazenou).

Dito isso, você pode simplesmente colocar seu código em uma função e executá-la com timeit.timeit():

def function_to_repeat():
    # ...

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

Isso desabilitaria a coleta de lixo, chamava repetidamente a function_to_repeat()função e cronometra a duração total dessas chamadas usando timeit.default_timer(), que é o relógio disponível mais preciso para sua plataforma específica.

Você deve mover o código de configuração para fora da função repetida; por exemplo, você deve se conectar ao banco de dados primeiro e depois cronometrar apenas as consultas. Use o setupargumento para importar ou criar essas dependências e passá-las para sua função:

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

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

iria pegar os globals function_to_repeat, var1e var2de seu roteiro e passar aqueles com a função de cada repetição.

Martijn Pieters
fonte
Colocar o código em uma função é um passo que eu estava procurando - já que simplesmente transformar o código em uma string e o evaling não voará por algo não completamente trivial. thx
javadba 28/09
2

Vejo que a pergunta já foi respondida, mas ainda quero adicionar meus 2 centavos pelo mesmo.

Também enfrentei um cenário semelhante no qual tenho que testar os tempos de execução para várias abordagens e, portanto, escrevi um pequeno script, que chama timeit em todas as funções nele escritas.

O script também está disponível como github gist aqui .

Espero que ajude você e outras pessoas.

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

Aqui está um invólucro simples para a resposta de steven. Esta função não faz repetidas execuções / média, apenas evita que você precise repetir o código de temporização em todos os lugares :)

'''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
fonte
0

O conjunto de testes não tenta usar o importado, timeitpor isso é difícil dizer qual era a intenção. No entanto, esta é uma resposta canônica, portanto um exemplo completo de timeitparece em ordem, elaborando a resposta de Martijn .

Os documentos paratimeit oferecem muitos exemplos e sinalizadores que valem a pena conferir. O uso básico na linha de comando é:

$ 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

Corra com -hpara ver todas as opções. O MOTW do Python tem uma ótima seção timeitque mostra como executar módulos através de cadeias de código de importação e multilinhas na linha de comando.

Em forma de script, eu normalmente uso assim:

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

Você pode facilmente inserir as funções e argumentos necessários. Tenha cuidado ao usar funções impuras e cuide do estado.

Saída de amostra:

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

Outro exemplo simples 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
fonte