Como usar o módulo timeit

351

Entendo o conceito do que timeitfaz, mas não tenho certeza de como implementá-lo no meu código.

Como posso comparar duas funções, digamos insertion_sorte tim_sort, com timeit?

Neemaximo
fonte

Respostas:

266

A maneira como o timeit funciona é executar o código de instalação uma vez e depois fazer chamadas repetidas para uma série de instruções. Portanto, se você deseja testar a classificação, são necessários alguns cuidados para que uma passagem em uma classificação no local não afete a próxima passagem com dados já classificados (que, é claro, fariam o Timsort realmente brilhar, porque apresenta melhor desempenho quando os dados já estiverem parcialmente solicitados).

Aqui está um exemplo de como configurar um teste para classificação:

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

Observe que a série de instruções faz uma nova cópia dos dados não classificados em cada passagem.

Além disso, observe a técnica de tempo de execução do conjunto de medições sete vezes e mantendo apenas o melhor tempo - isso pode realmente ajudar a reduzir distorções de medição devido a outros processos em execução no seu sistema.

Essas são minhas dicas para usar o timeit corretamente. Espero que isto ajude :-)

Raymond Hettinger
fonte
8
Sim, inclui a cópia da lista (que é muito rápida em comparação com a classificação em si). Se você não copiar, no entanto, a primeira passagem classifica a lista e a restante passagem não precisa fazer nenhum trabalho. Se você quiser saber o tempo apenas para o tipo, em seguida, executar o acima com e sem o timsort(a)e tirar a diferença :-)
Raymond Hettinger
Eu recomendo repetir 7 vezes para cada instalação e, em seguida, média; ao invés do contrário. Dessa forma, se cada pico devido a outros processos tiver uma boa chance de ser totalmente ignorado, em vez de ficar em média.
máx
75
@max Use o min () em vez da média dos tempos. Essa é uma recomendação minha, de Tim Peters e de Guido van Rossum. O tempo mais rápido representa o melhor que um algoritmo pode executar quando os caches são carregados e o sistema não está ocupado com outras tarefas. Todos os horários são barulhentos - o tempo mais rápido é o menos barulhento. É fácil mostrar que os tempos mais rápidos são os mais reproduzíveis e, portanto, os mais úteis ao cronometrar duas implementações diferentes.
Raymond Hettinger
4
Você calcula uma média (total, total, mas é equivalente) para 1000 entradas; repita 7 vezes e tome o mínimo . Você precisa da média de mais de 1000 entradas, porque deseja a complexidade média do algoritmo (não o melhor caso). Você precisa do mínimo exatamente pelo motivo que deu. Pensei em melhorar sua abordagem escolhendo uma entrada, executando o algoritmo 7 vezes, tomando o mínimo; repetindo-o para 1000 entradas diferentes e calculando a média. O que eu não percebi é que você .repeat(7,1000)já faz isso (usando a mesma semente)! Portanto, sua solução é perfeita IMO.
máx
5
Só posso acrescentar que a maneira como você aloca seu orçamento de 7000 execuções (por exemplo, .repeat(7, 1000)vs. .repeat(2, 3500)vs .repeat(35, 200) deve depender de como o erro devido à carga do sistema se compara ao erro devido à variabilidade de entrada. No caso extremo, se o seu sistema estiver sempre sob carga pesada e você vir uma cauda longa e fina à esquerda da distribuição do tempo de execução (quando você o pega em um raro estado inativo), você pode até achar .repeat(7000,1)mais útil do que .repeat(7,1000)se não pode orçar mais de 7000 execuções.
Max
277

Se você deseja usar timeitem uma sessão interativa do Python, há duas opções convenientes:

  1. Use o shell IPython . Possui a %timeitfunção especial conveniente :

    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. Em um intérprete padrão do Python, você pode acessar funções e outros nomes que você definiu anteriormente durante a sessão interativa importando-os da __main__instrução 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
fonte
97
+1 para mostrar a from __main__ import ftécnica. Eu não acho que isso seja tão conhecido como deveria ser. É útil em casos como este em que uma chamada de função ou método está sendo cronometrada. Em outros casos (cronometrando uma série de etapas), é menos útil porque introduz uma sobrecarga de chamada de função.
Raymond Hettinger
15
Você pode simplesmente fazer%timeit f(x)
qed
Nota: a configuração "import f" faz o acesso a uma leitura local rápida - o que não reflete exatamente uma chamada de função global (de função rápida curta) no código normal típico. No Py3.5 +, globals reais podem ser fornecidos: "Alterado na versão 3.5: O parâmetro globals opcional foi adicionado."; Antes das globais do módulo timeit, era inevitável (o que não faz muito sentido). Possivelmente, as globais do código de chamada ( sys._getframe(N).f_globals) deveriam ter sido o padrão desde o início.
Kxr # 6/17
140

Vou contar um segredo: a melhor maneira de usar timeité na linha de comando.

Na linha de comando, timeitfaz uma análise estatística adequada: informa quanto tempo levou a menor execução. Isso é bom porque todo erro no tempo é positivo. Portanto, o menor tempo tem o menor erro. Não há como obter um erro negativo, porque um computador nunca pode calcular mais rápido do que ele pode calcular!

Portanto, a interface da linha de comandos:

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

Isso é bem simples, né?

Você pode configurar as coisas:

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

o que também é útil!

Se você desejar várias linhas, poderá usar a continuação automática do shell ou argumentos separados:

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

Isso fornece uma configuração de

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

e tempos

sum(x)
min(y)

Se você deseja ter scripts mais longos, pode ser tentado a mudar para timeitdentro de um script Python. Sugiro evitar isso porque a análise e o tempo são simplesmente melhores na linha de comando. Em vez disso, costumo criar scripts de 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

Isso pode demorar um pouco mais devido às múltiplas inicializações, mas normalmente isso não é grande coisa.


Mas e se você quiser usar timeitdentro do seu módulo?

Bem, a maneira mais simples é fazer:

def function(...):
    ...

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

e isso dá a você tempo cumulativo ( não mínimo!) para executar esse número de vezes.

Para obter uma boa análise, use .repeate use o mínimo:

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

Você normalmente deve combinar isso com, em functools.partialvez de lambda: ...diminuir a sobrecarga. Assim, você pode ter algo como:

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

Você também pode fazer:

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

o que proporcionaria algo mais próximo da interface na linha de comando, mas de uma maneira muito menos interessante. O "from __main__ import ..."permite usar o código do seu módulo principal dentro do ambiente artificial criado por timeit.

Vale a pena notar que este é um invólucro de conveniência Timer(...).timeit(...)e, portanto, não é particularmente bom no momento. Pessoalmente, prefiro usar Timer(...).repeat(...)como mostrei acima.


Advertências

Existem algumas ressalvas com timeitisso em todos os lugares.

  • As despesas gerais não são contabilizadas. Digamos que você queira tempo x += 1para descobrir quanto tempo leva a adição:

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

    Bem, não são 0,0476 µs. Você só sabe que é menos que isso. Todo erro é positivo.

    Portanto, tente encontrar uma sobrecarga pura :

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

    Essa é uma boa sobrecarga de 30% apenas por tempo! Isso pode distorcer enormemente os tempos relativos. Mas você realmente se importava apenas com a adição de horários; os tempos de pesquisa xtambém precisam ser incluídos nas despesas gerais:

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

    A diferença não é muito maior, mas está lá.

  • Métodos de mutação são perigosos.

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

    Mas isso está completamente errado! xé a lista vazia após a primeira iteração. Você precisará reinicializar:

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

    Mas então você tem muita sobrecarga. Conta isso separadamente.

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

    Observe que subtrair a sobrecarga é razoável aqui apenas porque a sobrecarga é uma fração pequena do tempo.

    No seu exemplo, vale a pena notar que tanto a Insertion Sort quanto a Tim Sort têm comportamentos de tempo completamente incomuns para listas já classificadas. Isso significa que você precisará de uma random.shuffleclassificação intermediária, se quiser evitar prejudicar seus horários.

Veedrac
fonte
11
o que significa usec? são microssegundos?
Hasan Iqbal
2
@HasanIqbalAnik Yes.
Veedrac
@StefanPochmann Porque ele não tenta amostrar várias vezes.
Veedrac
Os leitores desta resposta também podem estar interessados ​​em Usar Python timeitem um programa, mas funcionando da mesma maneira que a linha de comando? .
Graham
@Veedrac Considerando que você afirma subtrair a sobrecarga de tempo puro, timeitexecuta uma passdeclaração quando nenhum argumento é fornecido, o que, é claro, leva algum tempo. Se algum argumento for fornecido, nãopass será executado, portanto, subtrair alguns usecs de cada momento seria incorreto. 0.014
Arne
99

Se você deseja comparar dois blocos de código / funções rapidamente, você pode fazer:

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

Acho que a maneira mais fácil de usar o timeit é pela linha de comando:

Dado test.py :

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

tempo de execução assim:

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

para mim, esta é a maneira mais rápida:

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


timeit.timeit(stmt=foo, number=1234567)
Rodrigo Laguna
fonte
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
fonte
7

Isso funciona muito bem:

  python -m timeit -c "$(cat file_name.py)"
Ohad Rubin
fonte
Qual seria o equivalente do Windows?
Shailen
2
Como você passa parâmetros, se o script exige algum?
Juuso Ohtonen
3

permite configurar o mesmo dicionário em cada um dos itens a seguir e testar o tempo de execução.

O argumento de configuração é basicamente configurar o dicionário

Número é para executar o código 1000000 vezes. Não é a configuração, mas o stmt

Quando você executa isso, pode ver que o índice é muito mais rápido que o obtido. Você pode executá-lo várias vezes para ver.

O código basicamente tenta obter o valor de c no dicionário.

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

Aqui estão os meus resultados, os seus serão diferentes.

por índice: 0.20900007452246427

por get: 0.54841166886888

Stryker
fonte
Qual versão do python você está usando?
Eduardo
3

basta passar todo o seu código como argumento 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
fonte
1
import timeit


def oct(x):
   return x*x


timeit.Timer("for x in range(100): oct(x)", "gc.enable()").timeit()
Yagmur SAHIN
fonte
O que é gc.enable()?
Robin Andrews
0

O módulo timeit interno funciona melhor na linha de comando do IPython.

Para cronometrar funções de dentro de um módulo:

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

Exemplo de como usar o interpretador Python REPL com função que aceita parâmetros.

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

Você criaria duas funções e depois executaria algo semelhante a isso. Observe que você deseja escolher o mesmo número de execução / execução para comparar apple com apple.
Isso foi testado no Python 3.7.

insira a descrição da imagem aqui Aqui está o código para facilitar a cópia

!/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
fonte