
Passo 1 de 9
Definir o que investigar na execução
Escolha uma carga representativa e uma pergunta que o perfilamento possa responder sobre a distribuição do tempo entre funções.
Trilha de aprendizado · Nível 14 · Tutorial 3
Analise a distribuição do tempo entre chamadas de funções para escolher uma hipótese de otimização apoiada em evidências.
Definir o que investigar na execução
Escolha uma carga representativa e uma pergunta que o perfilamento possa responder sobre a distribuição do tempo entre funções. 2 min
Executar o script e salvar o perfil
Execute uma carga local completa sob o cProfile e salve as estatísticas para consulta posterior. 3 min
Consultar e organizar o relatório com pstats
Abra o arquivo de perfil salvo, ordene as estatísticas e restrinja a apresentação às linhas úteis para a investigação. 2 min
Separar tempo próprio de tempo acumulado
Leia tottime e cumtime para distinguir o trabalho executado por uma função do trabalho que ela coordena por meio de outras chamadas. 3 min
Relacionar custo e quantidade de chamadas
Use ncalls e percall para distinguir uma função lenta por execução de uma operação pequena que se torna relevante pela repetição. 2 min
Descobrir de onde vem o trabalho
Siga as relações de chamada do perfil para descobrir qual função provoca uma repetição observada. 2 min
Reconhecer os limites da evidência
Delimite o que um perfil com cProfile mostra — e o que ainda precisa ser investigado e medido fora dele. 2 min
Transformar o diagnóstico em uma hipótese
Converta evidências do perfil em uma mudança pequena, verificável e segura. 3 min
Validar a mudança e concluir a investigação
Aplique a alteração, preserve a correção e decida com base em perfis, testes e medições sem instrumentação. 4 min

Passo 1 de 9
Escolha uma carga representativa e uma pergunta que o perfilamento possa responder sobre a distribuição do tempo entre funções.
Você já usou timeit para responder: quanto tempo este trecho demora? Agora a pergunta muda: em quais funções o tempo se concentra durante uma execução?
O cProfile faz um perfilamento determinístico: acompanha eventos de chamada e retorno de funções e agrega tempos e quantidades de chamadas por função. Ele ajuda a encontrar pontos para investigar; não prova, sozinho, qual alteração deve ser feita.
Exemplo
Um perfil descreve somente o caminho que foi executado, com aquele volume e aquela distribuição de dados. Se a carga não aciona o comportamento que preocupa você, o diagnóstico também não o mostrará.
Para este tutorial, usaremos um cenário determinístico: dados sintéticos locais alimentam funções encadeadas que processam textos e montam um relatório. Ele é uma carga didática, não o retrato de uma aplicação real.
A imagem mostra as etapas que serão incluídas em uma única execução observada.

O perfil observará a execução completa: preparação dos dados, processamento dos textos e montagem do relatório.
Dica
Escreva uma pergunta ligada ao comportamento real: “Onde o tempo se concentra ao processar este volume de textos?” Evite perfilar uma entrada minúscula ou um caminho que não representa o uso investigado.
Você percebe lentidão ao gerar relatórios de muitos textos. Qual opção define melhor uma investigação inicial com cProfile?

Passo 2 de 9
Execute uma carga local completa sob o cProfile e salve as estatísticas para consulta posterior.
Crie um arquivo chamado relatorio_textos.py em uma pasta local. Ele gera sempre os mesmos dados, processa cada texto e imprime um resumo. O if __name__ == "__main__" garante que a carga só rode quando o arquivo for executado diretamente.
Copie o script completo para um arquivo local com esse nome.
def gerar_textos(quantidade: int) -> list[str]:
bases = [
"Python torna a automação mais clara",
"Dados bem organizados facilitam análises",
"Perfis revelam onde o programa trabalha",
"Medições orientam mudanças cuidadosas",
]
return [f" {bases[indice % len(bases)]} #{indice} " for indice in range(quantidade)]
def normalizar(texto: str) -> str:
return " ".join(texto.lower().split())
def contar_palavras(texto: str) -> int:
return len(normalizar(texto).split())
def processar_textos(textos: list[str]) -> dict[str, int]:
total_palavras = 0
textos_com_python = 0
for texto in textos:
texto_normalizado = normalizar(texto)
total_palavras += contar_palavras(texto)
if "python" in texto_normalizado:
textos_com_python += 1
return {
"textos": len(textos),
"palavras": total_palavras,
"com_python": textos_com_python,
}
def montar_relatorio(resumo: dict[str, int]) -> str:
return (
f"Textos processados: {resumo['textos']}\n"
f"Palavras encontradas: {resumo['palavras']}\n"
f"Textos com Python: {resumo['com_python']}"
)
def main() -> None:
textos = gerar_textos(20_000)
resumo = processar_textos(textos)
print(montar_relatorio(resumo))
if __name__ == "__main__":
main()
Ao executar esse script com o perfilador, não será observado apenas o laço de processamento. A geração dos textos, o processamento, a montagem do relatório e outras chamadas feitas durante a execução também entram no perfil. Por isso, a carga precisa representar a pergunta que você quer investigar.
O perfil é produzido enquanto o programa realiza seu trabalho normal.

A execução produz dois resultados: o relatório visível e as estatísticas salvas pelo cProfile.
Dica
Você deve ver três linhas de resumo. Os números esperados para esta carga são 20.000 textos, 120.000 palavras e 5.000 textos com Python. O tempo de execução pode variar conforme seu computador.
No terminal, entre na pasta onde está relatorio_textos.py e execute o comando abaixo. A opção -o grava as estatísticas no arquivo local relatorio.prof.
Execute este comando na mesma pasta do script.
python -m cProfile -o relatorio.prof relatorio_textos.pyAtenção
relatorio.prof é um arquivo de dados do perfil, não um texto para abrir e interpretar manualmente. No próximo passo, você usará pstats para consultar suas estatísticas. O relatório impresso no terminal continua sendo a saída funcional normal do programa.
Relate o comando que você executou, o resumo que apareceu no terminal e o nome do arquivo de perfil criado na sua pasta.
Escreva pelo menos 40 caracteres (0/40).

Passo 3 de 9
Abra o arquivo de perfil salvo, ordene as estatísticas e restrinja a apresentação às linhas úteis para a investigação.
O arquivo criado com cProfile -o perfil.prof ... guarda dados do perfil em formato próprio. Para consultá-lo, crie um arquivo como consultar_perfil.py na mesma pasta e carregue-o com pstats.Stats.
Essa consulta apenas lê e apresenta dados já coletados: ela não executa novamente o script perfilado.
Salve este código como consultar_perfil.py e execute: python consultar_perfil.py
import pstats
estatisticas = pstats.Stats("perfil.prof")
estatisticas.sort_stats("cumulative")
estatisticas.print_stats(10)Cada linha se refere a uma função registrada no perfil.

A identificação costuma ter o formato <arquivo>:<linha>(<função>). O número aponta para a definição da função, não para uma instrução interna que necessariamente consumiu tempo.
Use sort_stats() antes de print_stats() para mudar a ordem exibida:
"cumulative" ordena pela coluna cumtime."time" ordena pela coluna tottime.Por enquanto, trate essas colunas como duas formas de organizar a investigação. No próximo step, você verá exatamente o que cada tempo inclui.
Exemplo
Para ver primeiro as linhas ordenadas por tottime:
<code>estatisticas.sort_stats("time")
estatisticas.print_stats(10)</code>
Você pode voltar a usar "cumulative" a qualquer momento. A ordenação altera somente a ordem do relatório na tela.
Coloque na ordem correta as ações de uma consulta por pstats.
print_stats(10) mostra apenas as 10 primeiras linhas na ordem atual. Você também pode passar um padrão textual para procurar funções pelo nome:
estatisticas.print_stats("normalizar")
Os filtros reduzem o que aparece no relatório. Eles não removem chamadas do arquivo perfil.prof nem mudam o trabalho que foi perfilado.
Execute as duas consultas sobre o mesmo arquivo de perfil.
import pstats
estatisticas = pstats.Stats("perfil.prof")
# Visão geral: primeiras 12 linhas por cumtime.
estatisticas.sort_stats("cumulative")
estatisticas.print_stats(12)
# Busca direcionada: linhas cujo identificador contém "normalizar".
estatisticas.sort_stats("time")
estatisticas.print_stats("normalizar")Dica
Confira se você está na pasta que contém perfil.prof e se escreveu parte do nome real da função. Um padrão mais curto, como "normal", pode ajudar a localizar a linha.
No seu computador, execute uma consulta com print_stats(10) e outra filtrando uma função do seu script, como print_stats("normalizar"). Em 2 ou 3 frases, diga qual critério de ordenação usou e explique o que o filtro mudou — e o que ele não mudou.
Escreva pelo menos 80 caracteres (0/80).

Passo 4 de 9
Leia tottime e cumtime para distinguir o trabalho executado por uma função do trabalho que ela coordena por meio de outras chamadas.
No relatório do cProfile, tottime é o tempo gasto no corpo da própria função, sem contar o tempo das funções que ela chamou. Já cumtime é o tempo acumulado: inclui o corpo da função e o trabalho de todas as chamadas descendentes feitas durante ela.
Assim, uma função que apenas organiza etapas pode ter tottime pequeno e cumtime alto.
Observe como o tempo da coordenadora inclui as funções que ela aciona.

O tempo acumulado da coordenadora contém seu tempo próprio mais o trabalho das descendentes.
Exemplo
Considere estas funções chamadas uma vez:
<code>ncalls tottime cumtime função
1 0.010 0.200 montar_relatorio
1 0.070 0.070 normalizar_textos
1 0.120 0.120 resumir_palavras</code>
montar_relatorio gastou 0,010 s no próprio corpo e chamou as duas funções abaixo. Por isso, seu cumtime é 0,200 s: 0,010 + 0,070 + 0,120.
Isso não prova que o corpo de montar_relatorio é caro. Neste caso, ela coordena trabalho concentrado nas descendentes.
Dica
Ordene por cumulative para encontrar caminhos completos de execução que concentram tempo. Ordene por time para destacar funções com maior trabalho próprio. As duas visões respondem perguntas diferentes e se complementam.
Faça as associações.
Toque em um item e depois no par correspondente.
Com as três linhas do exemplo, qual conclusão está correta sobre o tempo total do trecho?

Passo 5 de 9
Use ncalls e percall para distinguir uma função lenta por execução de uma operação pequena que se torna relevante pela repetição.
Além de tottime e cumtime, o relatório mostra ncalls: a quantidade de chamadas registradas para uma função.
A primeira coluna percall é tottime ÷ ncalls: o tempo próprio médio por chamada. A segunda é cumtime ÷ chamadas primitivas: o tempo acumulado médio por chamada primitiva.
Não escolha um alvo apenas pela maior contagem. Uma função pode ser cara em poucas execuções, ou barata individualmente e ainda assim consumir tempo relevante quando se repete milhares de vezes.
Compare custo por chamada, frequência e contribuição total.

A contribuição para a carga depende da combinação entre o custo de cada chamada e a quantidade de repetições.
Exemplo
ncalls tottime percall cumtime percall função
4 0.480 0.120 0.480 0.120 calcular_indicador
80000 0.640 0.000 0.700 0.000 normalizar_palavracalcular_indicador tem custo próprio médio alto: cada uma das 4 chamadas leva cerca de 0,120 s. Já normalizar_palavra parece barata por chamada, mas suas 80.000 execuções somam 0,640 s de tempo próprio. O 0.000 exibido foi arredondado; não significa custo zero.
Em uma função recursiva, ncalls pode aparecer como total/primitivas, por exemplo, 8/3.
Nessa linha, o primeiro percall usa 8 como denominador; o segundo usa 3. Essa separação evita tratar cada reentrada recursiva como se fosse um início independente do caminho.
Exemplo
ncalls tottime percall cumtime percall função
8/3 0.024 0.003 0.060 0.020 explorar_ramoCálculos aproximados:
percall: 0,024 ÷ 8 = 0,003 s de tempo próprio por chamada total.percall: 0,060 ÷ 3 = 0,020 s de tempo acumulado por chamada primitiva.Os valores são médias da execução registrada; não afirmam que toda chamada teve exatamente a mesma duração.
Na linha 8/3, o segundo percall é calculado dividindo cumtime por ___.
Considere estas linhas do perfil:
ncalls tottime percall função
5 0.250 0.050 funcao_a
50000 0.900 0.000 funcao_bQual afirmação é mais bem apoiada por esses dados?

Passo 6 de 9
Siga as relações de chamada do perfil para descobrir qual função provoca uma repetição observada.
Uma linha do perfil pode mostrar que normalizar_texto concentra tempo ou possui muitas chamadas. Isso ainda não responde quem decidiu chamá-la tantas vezes.
Use as relações de chamada para separar as perguntas:
print_callers("normalizar_texto"): quem chama a função investigada?print_callees("processar_registro"): quais funções são chamadas por essa função?No cenário do relatório, uma chamada repetida de normalizar_texto pode aparecer como custo nessa função, mas a frequência pode ser determinada pelo corpo de processar_registro.
Leia as relações como setas entre funções. Para investigar a repetição, comece na função que aparece no relatório e siga para o chamador.

O custo é registrado em normalizar_texto; a decisão de chamá-la duas vezes por registro está em processar_registro.
Dica
As contagens e os tempos das relações ajudam a rastrear o caminho do trabalho. Continue interpretando tottime, cumtime e ncalls como medidas relacionadas, sem somar tempos de funções ancestrais e descendentes como se fossem trabalhos independentes.
Crie um arquivo chamado consultar_chamadas.py na mesma pasta de relatorio.prof, gerado anteriormente. O filtro pelo nome reduz a apresentação a relações relevantes; ele não altera a execução que foi perfilada.
Consulta as relações de chamada no perfil salvo.
import pstats
perfil = pstats.Stats("relatorio.prof")
perfil.strip_dirs()
print("CHAMADORES DE normalizar_texto")
perfil.print_callers("normalizar_texto")
print("\nFUNÇÕES CHAMADAS POR processar_registro")
perfil.print_callees("processar_registro")
Exemplo
Execute python consultar_chamadas.py.
A saída de print_callers("normalizar_texto") lista as funções que chamaram normalizar_texto, com as contagens e tempos associados àquela relação. Se processar_registro aparece ali com uma contagem próxima de duas vezes o número de registros, essa é uma evidência de que esse chamador determina a repetição.
Já print_callees("processar_registro") mostra as funções acionadas por processar_registro. Procure normalizar_texto nessa lista para observar a mesma ligação no sentido oposto. A formatação e os valores exatos dependem da sua execução e da versão do Python.
Associe cada pergunta ao comando mais útil.
Toque em um item e depois no par correspondente.
Execute a consulta no seu perfil. Qual comando responde à sua pergunta e qual relação observada indica onde investigar a origem das chamadas repetidas? Mencione os nomes das funções que apareceram na sua saída.
Escreva pelo menos 80 caracteres (0/80).

Passo 7 de 9
Delimite o que um perfil com cProfile mostra — e o que ainda precisa ser investigado e medido fora dele.
O cProfile acompanha chamadas e retornos para distribuir o tempo entre funções da execução observada. Essa instrumentação também acrescenta trabalho à execução, portanto os tempos do relatório não são a medição final de desempenho do programa sem perfilador.
Use o perfil para perguntar onde investigar primeiro. Em especial, muitas chamadas muito curtas podem sofrer distorção relativa maior pela sobrecarga do acompanhamento.
Leia o relatório como um mapa da carga executada.

O perfil ajuda a localizar concentrações; a confirmação de ganho exige medir sem o cProfile ativo.
Atenção
“Esta função levou 0,8 s no cProfile; logo, a aplicação ficará exatamente 0,8 s mais rápida se eu a melhorar” é uma conclusão indevida. O tempo instrumentado e a duração real sem instrumentação não são equivalentes.
O cProfile agrega estatísticas por função. Uma linha de uma função não recebe uma linha própria no relatório: um cumtime alto identifica a função como candidata, mas não prova qual instrução interna consome o custo.
Uma chamada a recurso nativo pode aparecer como uma entrada no perfil, porém seu trabalho interno não é detalhado em novas funções Python. E um perfil de um processo não incorpora automaticamente o trabalho feito por outros processos.
O relatório mostra chamadas da execução perfilada, com detalhes até o nível de função Python.

Linhas internas, detalhes nativos e trabalho de outro processo não são explicados automaticamente pelo perfil atual.
Se normalizar_texto concentra tempo ou chamadas no perfil, isso prova que alterar essa função melhorará o desempenho geral em qualquer carga.
Qual conclusão é compatível com um relatório do cProfile de um único processo?

Passo 8 de 9
Converta evidências do perfil em uma mudança pequena, verificável e segura.
Use o perfil para formular uma explicação que possa falhar em um teste. Uma hipótese útil tem quatro partes:
O perfil indica onde investigar; ele não prova sozinho que uma alteração melhorará o programa.

A alteração deve derivar de uma evidência e produzir uma previsão observável.
Exemplo
Suponha que o perfil e as relações de chamada indiquem que processar_registro chama normalizar_texto repetidamente para o mesmo texto.
Versão de referência
def processar_registro(texto):
palavras = normalizar_texto(texto).split()
tamanho = len(normalizar_texto(texto))
return {"palavras": len(palavras), "caracteres": tamanho}Alteração proposta
def processar_registro(texto):
normalizado = normalizar_texto(texto)
palavras = normalizado.split()
tamanho = len(normalizado)
return {"palavras": len(palavras), "caracteres": tamanho}Hipótese: como as duas chamadas recebem a mesma entrada dentro da mesma iteração, calcular uma vez e reutilizar o resultado reduzirá chamadas redundantes, mantendo o relatório igual.
Dica
Confirme duas condições: as entradas realmente são iguais e a função não precisa ser repetida por seus efeitos colaterais. Reutilização é apropriada quando o resultado depende daquela entrada e repetir a chamada não é parte do comportamento exigido.
A previsão para o novo perfil é específica: normalizar_texto deve aparecer com menos chamadas relacionadas a cada registro processado. Não exija antecipadamente uma porcentagem fixa de redução no tempo.
Para decidir, preserve a versão de referência, faça uma mudança por vez, teste a equivalência dos resultados e compare a carga completa das duas versões com medições repetidas de timeit, sem cProfile ativo. Use entradas e condições equivalentes.
Coloque o protocolo em uma ordem útil para investigar a hipótese.
Com base em uma função de normalização chamada repetidamente pelo mesmo processamento de registro, redija uma hipótese contendo evidência, explicação possível, alteração, previsão para o perfil e critério de validação fora do perfilador.
Escreva pelo menos 180 caracteres (0/180).

Passo 9 de 9
Aplique a alteração, preserve a correção e decida com base em perfis, testes e medições sem instrumentação.
A hipótese anterior era evitar a normalização repetida do mesmo texto dentro de cada registro. A versão alterada calcula o valor uma vez e o reutiliza.
O código abaixo mantém a versão de referência para comparação, testa entradas relevantes e mede as duas versões com a mesma carga. Salve-o como relatorio_validado.py e execute-o no seu computador.

A alteração reduz uma chamada redundante por registro, sem mudar o relatório produzido.
Execute este arquivo primeiro sem o perfilador para rodar as verificações e as medições.
from __future__ import annotations
import sys
import timeit
def gerar_registros(quantidade: int = 4_000) -> list[str]:
bases = [
" Python mede antes de otimizar ",
"Perfis mostram chamadas repetidas",
"Dados representativos orientam decisoes",
"Uma mesma entrada gera o mesmo relatorio",
]
return [bases[indice % len(bases)] for indice in range(quantidade)]
def normalizar(texto: str) -> str:
return " ".join(texto.lower().split())
def analisar_referencia(texto: str) -> dict[str, object]:
palavras = normalizar(texto).split()
return {
"texto": normalizar(texto),
"palavras": len(palavras),
"primeira": palavras[0] if palavras else "",
}
def analisar_alterada(texto: str) -> dict[str, object]:
texto_normalizado = normalizar(texto)
palavras = texto_normalizado.split()
return {
"texto": texto_normalizado,
"palavras": len(palavras),
"primeira": palavras[0] if palavras else "",
}
def gerar_relatorio(registros: list[str], analisador: object) -> dict[str, object]:
funcao = analisador
itens = [funcao(texto) for texto in registros] # type: ignore[operator]
return {
"quantidade": len(itens),
"palavras": sum(item["palavras"] for item in itens),
"primeiros": [item["primeira"] for item in itens[:3]],
"itens": itens,
}
def executar_referencia(registros: list[str]) -> dict[str, object]:
return gerar_relatorio(registros, analisar_referencia)
def executar_alterada(registros: list[str]) -> dict[str, object]:
return gerar_relatorio(registros, analisar_alterada)
def verificar_equivalencia() -> None:
casos = [
[],
[" "],
["Python Python"],
gerar_registros(30),
]
for registros in casos:
esperado = executar_referencia(registros)
obtido = executar_alterada(registros)
assert obtido == esperado, f"Resultado diferente para {registros!r}"
print("Testes de equivalência: aprovados")
def medir(funcao: object, registros: list[str]) -> list[float]:
return timeit.repeat(
lambda: funcao(registros), # type: ignore[operator]
repeat=5,
number=10,
)
def mostrar_medicoes() -> None:
registros = gerar_registros()
referencia = medir(executar_referencia, registros)
alterada = medir(executar_alterada, registros)
print(f"Referência — menor de 5: {min(referencia):.6f} s / 10 execuções")
print(f"Alterada — menor de 5: {min(alterada):.6f} s / 10 execuções")
def executar_para_perfil(versao: str) -> None:
registros = gerar_registros()
if versao == "referencia":
resultado = executar_referencia(registros)
elif versao == "alterada":
resultado = executar_alterada(registros)
else:
raise SystemExit("Use referencia ou alterada após --perfil.")
print(resultado["quantidade"], resultado["palavras"])
if __name__ == "__main__":
if len(sys.argv) == 3 and sys.argv[1] == "--perfil":
executar_para_perfil(sys.argv[2])
else:
verificar_equivalencia()
mostrar_medicoes()Execute python relatorio_validado.py. As verificações precisam passar antes de qualquer decisão de desempenho.
Em seguida, gere um perfil para cada versão. Os tempos absolutos podem variar; compare principalmente a contagem de normalizar e consulte as mesmas funções nos dois arquivos.
Rode os comandos no diretório em que você salvou o arquivo.
python relatorio_validado.py
python -m cProfile -o perfil_referencia.prof relatorio_validado.py --perfil referencia
python -m cProfile -o perfil_alterada.prof relatorio_validado.py --perfil alteradaSalve como consultar_perfis.py e execute python consultar_perfis.py. A seleção por nome limita a apresentação; ela não altera o que foi perfilado.
import pstats
for arquivo in ("perfil_referencia.prof", "perfil_alterada.prof"):
print(f"\n=== {arquivo} ===")
estatisticas = pstats.Stats(arquivo)
estatisticas.strip_dirs().sort_stats("cumulative").print_stats("normalizar")
estatisticas.sort_stats("time").print_stats("normalizar")Escreva um registro curto da sua investigação. Qual função e qual chamador você examinou? O que ocorreu com as chamadas no segundo perfil? Os resultados permaneceram equivalentes? Com base nas medições sem cProfile, você aceita, rejeita ou considera inconclusiva a hipótese?
Escreva pelo menos 300 caracteres (0/300).
Resumo
Um perfil aponta onde investigar; ele não prova sozinho que uma alteração melhorou o programa.
timeit sem cProfile ativo, repetindo a mesma carga completa sob condições equivalentes.Parabéns! Você concluiu: Localizar gargalos de execução com cProfile
Milhares de cursos online em vídeo, ebooks e áudiobooks.
Para testar seus conhecimentos no decorrer dos cursos online
Gerado diretamente na galeria de fotos do seu celular e enviado ao seu e-mail
Baixe nosso aplicativo pelo QR Code ou pelos links abaixo:.
+ de 10 milhões
de alunos
Certificado grátis e
válido em todo o Brasil
60 mil exercícios
gratuitos
4,8/5 classificação
nas lojas de apps
Cursos gratuitos em
vídeo, ebooks e audiobooks