Benchmarking e Profiling
Neste capítulo, aprenderemos como benchmarking e criação de perfil ajudam a resolver problemas de desempenho.
Suponha que tenhamos escrito um código e ele esteja dando o resultado desejado também, mas e se quisermos executar esse código um pouco mais rápido porque as necessidades mudaram. Nesse caso, precisamos descobrir quais partes do nosso código estão tornando o programa inteiro mais lento. Nesse caso, benchmarking e criação de perfil podem ser úteis.
O que é Benchmarking?
O benchmarking visa avaliar algo em comparação com um padrão. No entanto, a questão que se coloca aqui é qual seria o benchmarking e por que precisamos dele no caso de programação de software. O benchmarking do código significa quão rápido o código está sendo executado e onde está o gargalo. Um dos principais motivos para o benchmarking é que ele otimiza o código.
Como funciona o benchmarking?
Se falamos sobre o funcionamento do benchmarking, precisamos começar comparando todo o programa como um estado atual, então podemos combinar micro benchmarks e então decompor um programa em programas menores. Para encontrar os gargalos dentro do nosso programa e otimizá-lo. Em outras palavras, podemos entendê-lo como dividir o grande e difícil problema em uma série de problemas menores e um pouco mais fáceis para otimizá-los.
Módulo Python para benchmarking
Em Python, temos um módulo padrão para benchmarking que é chamado timeit. Com a ajuda dotimeit módulo, podemos medir o desempenho de um pequeno pedaço de código Python em nosso programa principal.
Exemplo
No seguinte script Python, estamos importando o timeit módulo, que mede ainda mais o tempo gasto para executar duas funções - functionA e functionB -
import timeit
import time
def functionA():
print("Function A starts the execution:")
print("Function A completes the execution:")
def functionB():
print("Function B starts the execution")
print("Function B completes the execution")
start_time = timeit.default_timer()
functionA()
print(timeit.default_timer() - start_time)
start_time = timeit.default_timer()
functionB()
print(timeit.default_timer() - start_time)
Após executar o script acima, obteremos o tempo de execução de ambas as funções conforme mostrado abaixo.
Resultado
Function A starts the execution:
Function A completes the execution:
0.0014599495514175942
Function B starts the execution
Function B completes the execution
0.0017024724827479076
Escrevendo nosso próprio cronômetro usando a função decorador
Em Python, podemos criar nosso próprio cronômetro, que funcionará exatamente como o timeitmódulo. Isso pode ser feito com a ajuda dodecoratorfunção. A seguir está um exemplo de cronômetro personalizado -
import random
import time
def timer_func(func):
def function_timer(*args, **kwargs):
start = time.time()
value = func(*args, **kwargs)
end = time.time()
runtime = end - start
msg = "{func} took {time} seconds to complete its execution."
print(msg.format(func = func.__name__,time = runtime))
return value
return function_timer
@timer_func
def Myfunction():
for x in range(5):
sleep_time = random.choice(range(1,3))
time.sleep(sleep_time)
if __name__ == '__main__':
Myfunction()
O script python acima ajuda na importação de módulos de tempo aleatório. Criamos a função decoradora timer_func (). Isso tem a função function_timer () dentro dele. Agora, a função aninhada pegará o tempo antes de chamar a função passada. Em seguida, ele aguarda o retorno da função e agarra o horário de término. Desta forma, podemos finalmente fazer o script python imprimir o tempo de execução. O script irá gerar a saída conforme mostrado abaixo.
Resultado
Myfunction took 8.000457763671875 seconds to complete its execution.
O que é criação de perfil?
Às vezes, o programador deseja medir alguns atributos como o uso de memória, complexidade de tempo ou uso de instruções específicas sobre os programas para medir a capacidade real desse programa. Esse tipo de medição sobre o programa é chamado de criação de perfil. A criação de perfil usa análise de programa dinâmica para fazer tal medição.
Nas seções subsequentes, aprenderemos sobre os diferentes módulos Python para criação de perfil.
cProfile - o módulo embutido
cProfileé um módulo integrado do Python para criação de perfil. O módulo é uma extensão C com sobrecarga razoável que o torna adequado para criar perfis de programas de longa execução. Após executá-lo, ele registra todas as funções e tempos de execução. É muito poderoso, mas às vezes um pouco difícil de interpretar e agir. No exemplo a seguir, estamos usando cProfile no código abaixo -
Exemplo
def increment_global():
global x
x += 1
def taskofThread(lock):
for _ in range(50000):
lock.acquire()
increment_global()
lock.release()
def main():
global x
x = 0
lock = threading.Lock()
t1 = threading.Thread(target=taskofThread, args=(lock,))
t2 = threading.Thread(target= taskofThread, args=(lock,))
t1.start()
t2.start()
t1.join()
t2.join()
if __name__ == "__main__":
for i in range(5):
main()
print("x = {1} after Iteration {0}".format(i,x))
O código acima é salvo no thread_increment.pyArquivo. Agora, execute o código com cProfile na linha de comando da seguinte maneira -
(base) D:\ProgramData>python -m cProfile thread_increment.py
x = 100000 after Iteration 0
x = 100000 after Iteration 1
x = 100000 after Iteration 2
x = 100000 after Iteration 3
x = 100000 after Iteration 4
3577 function calls (3522 primitive calls) in 1.688 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
5 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap>:103(release)
5 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap>:143(__init__)
5 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap>:147(__enter__)
… … … …
A partir da saída acima, fica claro que cProfile imprime todas as funções 3577 chamadas, com o tempo gasto em cada uma e o número de vezes que foram chamadas. A seguir estão as colunas que obtivemos na saída -
ncalls - É o número de ligações feitas.
tottime - É o tempo total gasto na função dada.
percall - Refere-se ao quociente de tottime dividido por ncalls.
cumtime- É o tempo acumulado gasto nesta e em todas as subfunções. É preciso até mesmo para funções recursivas.
percall - É o quociente de tempo cum dividido por chamadas primitivas.
filename:lineno(function) - Fornece basicamente os respectivos dados de cada função.