timeit — Mede o tempo de execução de pequenos trechos de código

Código-fonte: Lib/timeit.py


Este módulo fornece uma maneira simples de cronometrar pequenos trechos do código Python. Ele tem uma Interface de Linha de Comando e um chamável. Ele evita uma série de armadilhas comuns para medir os tempos de execução de um script. Veja também o capítulo “Algorithms” de Tim Peters, na segunda edição do Python Cookbook, publicado pela O’Reilly.

Exemplos básicos

O exemplo a seguir mostra como a Interface de Linha de Comando pode ser usado para comparar três expressões diferentes:

$ python -m timeit "'-'.join(str(n) for n in range(100))"
10000 loops, best of 5: 30.2 usec per loop
$ python -m timeit "'-'.join([str(n) for n in range(100)])"
10000 loops, best of 5: 27.5 usec per loop
$ python -m timeit "'-'.join(map(str, range(100)))"
10000 loops, best of 5: 23.2 usec per loop

Isso pode ser obtido da interface Interface em Python

>>> import timeit
>>> timeit.timeit('"-".join(str(n) for n in range(100))', number=10000)
0.3018611848820001
>>> timeit.timeit('"-".join([str(n) for n in range(100)])', number=10000)
0.2727368790656328
>>> timeit.timeit('"-".join(map(str, range(100)))', number=10000)
0.23702679807320237

Um chamável também pode ser passado para a Interface em Python:

>>> timeit.timeit(lambda: "-".join(map(str, range(100))), number=10000)
0.19665591977536678

Observe, entretanto, que timeit() determinará automaticamente o número de repetições somente quando a interface de linha de comando for usada. Na seção Exemplos você encontrará exemplos mais avançados.

Interface em Python

Este módulo define três funções e uma classe pública:

timeit.timeit(stmt='pass', setup='pass', timer=<default timer>, number=1000000, globals=None)

Cria uma instância de Timer com o código de setup e a função timer função para executar o método timeit() com o total de execuções informado em number. O argumento opcional globals especifica um espaço de nomes no qual o código será executado.

Alterado na versão 3.5: O parâmetro opcional globals foi adicionado.

timeit.repeat(stmt='pass', setup='pass', timer=<default timer>, repeat=5, number=1000000, globals=None)

Create a Timer instance with the given statement, setup code and timer function and run its repeat() method with the given repeat count and number executions. The optional globals argument specifies a namespace in which to execute the code.

Alterado na versão 3.5: O parâmetro opcional globals foi adicionado.

Alterado na versão 3.7: Valor padrão de repetição mudou de 3 para 5.

timeit.default_timer()

O cronômetro padrão, que é sempre time.perf_counter(), retorna segundos com ponto flutuante. Uma alternativa, time.perf_counter_ns, retorna um inteiro em nanossegundos.

Alterado na versão 3.3: time.perf_counter() é o cronômetro padrão agora.

class timeit.Timer(stmt='pass', setup='pass', timer=<timer function>, globals=None)

Classe para cronometrar a velocidade de execução de pequenos trechos de código.

The constructor takes a statement to be timed, an additional statement used for setup, and a timer function. Both statements default to 'pass'; the timer function is platform-dependent (see the module doc string). stmt and setup may also contain multiple statements separated by ; or newlines, as long as they don’t contain multi-line string literals. The statement will by default be executed within timeit’s namespace; this behavior can be controlled by passing a namespace to globals.

Para medir o tempo de execução da primeira instrução use o método timeit(). Os métodos repeat() e autorange() são convenientes para chamar timeit() várias vezes.

O tempo de execução de setup é excluído do tempo total de execução cronometrado.

The stmt and setup parameters can also take objects that are callable without arguments. This will embed calls to them in a timer function that will then be executed by timeit(). Note that the timing overhead is a little larger in this case because of the extra function calls.

Alterado na versão 3.5: O parâmetro opcional globals foi adicionado.

timeit(number=1000000)

Time number executions of the main statement. This executes the setup statement once, and then returns the time it takes to execute the main statement a number of times. The default timer returns seconds as a float. The argument is the number of times through the loop, defaulting to one million. The main statement, the setup statement and the timer function to be used are passed to the constructor.

Nota

By default, timeit() temporarily turns off garbage collection during the timing. The advantage of this approach is that it makes independent timings more comparable. The disadvantage is that GC may be an important component of the performance of the function being measured. If so, GC can be re-enabled as the first statement in the setup string. For example:

timeit.Timer('for i in range(10): oct(i)', 'gc.enable()').timeit()
autorange(callback=None)

Determina automaticamente quantas vezes chamar timeit().

This is a convenience function that calls timeit() repeatedly so that the total time >= 0.2 second, returning the eventual (number of loops, time taken for that number of loops). It calls timeit() with increasing numbers from the sequence 1, 2, 5, 10, 20, 50, … until the time taken is at least 0.2 seconds.

Se callback for fornecido e não for None, ele será chamado após cada tentativa e tem dois argumento: callback(number, time_taken).

Adicionado na versão 3.6.

repeat(repeat=5, number=1000000)

Chama timeit() algumas vezes.

Esse é um função de conveniência que chama o timeit() repetidamente e retorna uma lista de resultados. O primeiro argumento especifica quantas vezes deve chamar o timeit(). O segundo argumento especifica o argumento number para timeit().

Nota

It’s tempting to calculate mean and standard deviation from the result vector and report these. However, this is not very useful. In a typical case, the lowest value gives a lower bound for how fast your machine can run the given code snippet; higher values in the result vector are typically not caused by variability in Python’s speed, but by other processes interfering with your timing accuracy. So the min() of the result is probably the only number you should be interested in. After that, you should look at the entire vector and apply common sense rather than statistics.

Alterado na versão 3.7: Valor padrão de repetição mudou de 3 para 5.

print_exc(file=None)

Função auxiliar para imprimir um traceback do código cronometrado.

Uso típico:

t = Timer(...)       # outside the try/except
try:
    t.timeit(...)    # or t.repeat(...)
except Exception:
    t.print_exc()

A vantagem em relação ao traceback padrão é que as linhas de origem no modelo compilado serão exibidas. O argumento opcional file direciona para onde o traceback é enviado; o padrão é sys.stderr.

Interface de Linha de Comando

Quando chamado como um programa a partir da linha de comando, as seguintes opções estão disponíveis:

python -m timeit [-n N] [-r N] [-u U] [-s S] [-p] [-v] [-h] [statement ...]

As seguintes opções são permitidas

-n N, --number=N

Quantas vezes deve executar ‘statement’

-r N, --repeat=N

Quantidade de vezes para repetir o cronômetro (o valor padrão é 5)

-s S, --setup=S

instrução a ser executada apenas uma vez e quando iniciada (padrão pass )

-p, --process

mede apenas o tempo de processamento, e não o tempo total de execução, usando time.process_time() em vez de time.perf_counter(), que é o padrão

Adicionado na versão 3.3.

-u, --unit=U

especifique uma unidade de tempo para a saída do cronômetro; pode selecionar nsec, usec, msec, ou sec

Adicionado na versão 3.5.

-v, --verbose

imprime resultados brutos de tempo; repetir para obter mais precisão de dígitos

-h, --help

imprime uma mensagem curta de uso e sai

A multi-line statement may be given by specifying each line as a separate statement argument; indented lines are possible by enclosing an argument in quotes and using leading spaces. Multiple -s options are treated similarly.

Se -n não for informada, um número adequado de loops será calculado tentando adicionar números numa sequência como 1, 2, 5, 10, 20, 50, … até que o tempo total seja de pelo menos 0,2 segundos.

default_timer() measurements can be affected by other programs running on the same machine, so the best thing to do when accurate timing is necessary is to repeat the timing a few times and use the best time. The -r option is good for this; the default of 5 repetitions is probably enough in most cases. You can use time.process_time() to measure CPU time.

Nota

Há uma certa sobrecarga padrão associada à execução de uma instrução pass. O código aqui não tenta ocultá-lo, mas você deve estar ciente disso. A sobrecarga padrão pode ser medida invocando pelo programa sem argumento, e pode ser diferente entre diferentes versões Python.

Exemplos

É possível fornecer uma instrução de configuração que é executada apenas uma vez no início:

$ python -m timeit -s "text = 'sample string'; char = 'g'" "char in text"
5000000 loops, best of 5: 0.0877 usec per loop
$ python -m timeit -s "text = 'sample string'; char = 'g'" "text.find(char)"
1000000 loops, best of 5: 0.342 usec per loop

Na saída, existem três campos. A contagem de laços, que informa quantas vezes o corpo da instrução foi executado por repetição do laço de temporização. A contagem de repetições (‘melhor de 5’) que informa quantas vezes o laço de temporização foi repetido e, finalmente, o tempo que o corpo da instrução levou, em média, na melhor repetição do laço de temporização. Ou seja, o tempo necessário para a repetição mais rápida dividido pela contagem de interações.

>>> import timeit
>>> timeit.timeit('char in text', setup='text = "sample string"; char = "g"')
0.41440500499993504
>>> timeit.timeit('text.find(char)', setup='text = "sample string"; char = "g"')
1.7246671520006203

O mesmo pode ser feito usando a classe Timer e seus métodos:

>>> import timeit
>>> t = timeit.Timer('char in text', setup='text = "sample string"; char = "g"')
>>> t.timeit()
0.3955516149999312
>>> t.repeat()
[0.40183617287970225, 0.37027556854118704, 0.38344867356679524, 0.3712595970846668, 0.37866875250654886]

Os exemplos a seguir mostram como cronometrar expressões que contêm várias linhas. Aqui comparamos o custo de usar hasattr() vs. try/except para testar atributos de objetos presentes e ausentes:

$ python -m timeit "try:" "  str.__bool__" "except AttributeError:" "  pass"
20000 loops, best of 5: 15.7 usec per loop
$ python -m timeit "if hasattr(str, '__bool__'): pass"
50000 loops, best of 5: 4.26 usec per loop

$ python -m timeit "try:" "  int.__bool__" "except AttributeError:" "  pass"
200000 loops, best of 5: 1.43 usec per loop
$ python -m timeit "if hasattr(int, '__bool__'): pass"
100000 loops, best of 5: 2.23 usec per loop
>>> import timeit
>>> # attribute is missing
>>> s = """\
... try:
...     str.__bool__
... except AttributeError:
...     pass
... """
>>> timeit.timeit(stmt=s, number=100000)
0.9138244460009446
>>> s = "if hasattr(str, '__bool__'): pass"
>>> timeit.timeit(stmt=s, number=100000)
0.5829014980008651
>>>
>>> # attribute is present
>>> s = """\
... try:
...     int.__bool__
... except AttributeError:
...     pass
... """
>>> timeit.timeit(stmt=s, number=100000)
0.04215312199994514
>>> s = "if hasattr(int, '__bool__'): pass"
>>> timeit.timeit(stmt=s, number=100000)
0.08588060699912603

Para dar ao módulo timeit acesso as funções que você definiu, você pode passar o parâmetro setup, que contém um instrução de importar:

def test():
    """Stupid test function"""
    L = [i for i in range(100)]

if __name__ == '__main__':
    import timeit
    print(timeit.timeit("test()", setup="from __main__ import test"))

Outra opção é passar globals() para o parâmetro globals, o que fará com que o código seja executado em seu espaço de nomes global. Isso pode ser mais conveniente do que especificar individualmente imports:

def f(x):
    return x**2
def g(x):
    return x**4
def h(x):
    return x**8

import timeit
print(timeit.timeit('[func(42) for func in (f,g,h)]', globals=globals()))