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?

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


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]

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.


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)

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

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)

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)

7

Isso funciona muito bem:

  python -m timeit -c "$(cat file_name.py)"

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


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

1
import timeit


def oct(x):
   return x*x


timeit.Timer("for x in range(100): oct(x)", "gc.enable()").timeit()

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

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                                                                                        

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")
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.