Trilha de aprendizado · Nível 14 · Tutorial 3

Localizar gargalos de execução com cProfile

Analise a distribuição do tempo entre chamadas de funções para escolher uma hipótese de otimização apoiada em evidências.

  • Nível: Intermediário
  • Duração: 20 min
  • 9 passos
Localizar gargalos de execução com cProfile

O que você vai percorrer

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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
  8. Transformar o diagnóstico em uma hipótese Converta evidências do perfil em uma mudança pequena, verificável e segura. 3 min
  9. 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

O que você vai aprender

  • Gerar um perfil de uma carga representativa e consultar seus resultados com pstats.
  • Distinguir tempo próprio, tempo acumulado e quantidade de chamadas.
  • Identificar processamento caro e repetição excessiva de chamadas.
  • Confirmar uma hipótese de melhoria com testes e medições fora do perfilador.

Antes de começar

  • Medir trechos de código com timeit
  • Decompor um programa em funções com responsabilidades claras
  • Controlar a execução de módulos com __name__

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.

Cronometrar ou localizar?

Duas perguntas diferentes

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

Perguntas adequadas

  • Timeit: “Qual versão desta função é mais rápida para esta entrada?”
  • Perfilamento: “No processamento completo do relatório, quais funções concentram mais trabalho ou são chamadas muitas vezes?”

A carga define a evidência

Perfil é retrato de uma execução

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.

Fluxo da carga didática

A imagem mostra as etapas que serão incluídas em uma única execução observada.

Diagrama de fluxo com registros de texto sintéticos passando por preparação, processamento em funções encadeadas e montagem de um relatório final; cada etapa é ligada por setas e as funções de processamento aparecem como nós internos.

O perfil observará a execução completa: preparação dos dados, processamento dos textos e montagem do relatório.

Dica

Defina antes de medir

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.

Escolha a pergunta e a carga

Qual investigação é adequada?

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

Executar o script e salvar o perfil

Execute uma carga local completa sob o cProfile e salve as estatísticas para consulta posterior.

Uma carga completa para investigar

Prepare o script

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.

relatorio_textos.py

Copie o script completo para um arquivo local com esse nome.

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

O que será acompanhado

Perfilar inclui todo o caminho executado

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.

Fluxo da execução perfilada

O perfil é produzido enquanto o programa realiza seu trabalho normal.

Diagrama mostrando dados sintéticos passando por geração, processamento de textos e montagem de relatório; uma camada de perfilamento acompanha todas as etapas e grava um arquivo separado.

A execução produz dois resultados: o relatório visível e as estatísticas salvas pelo cProfile.

Dica

Resultado previsível

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.

Execute e grave as estatísticas

Use cProfile pelo terminal

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.

Comando de execução

Execute este comando na mesma pasta do script.

bash
python -m cProfile -o relatorio.prof relatorio_textos.py

Atenção

O arquivo .prof não é um relatório para leitura direta

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.

Confirme os dois resultados

Registro da sua execução

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

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.

Do arquivo de perfil à consulta

Abra as estatísticas salvas

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.

Consulta inicial completa

Salve este código como consultar_perfil.py e execute: python consultar_perfil.py

python
import pstats

estatisticas = pstats.Stats("perfil.prof")
estatisticas.sort_stats("cumulative")
estatisticas.print_stats(10)

Como ler a identificação de uma linha

Cada linha se refere a uma função registrada no perfil.

Diagrama de uma linha de relatório de perfil dividida em arquivo, número da linha de definição e nome da função; uma lupa destaca cada parte.

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.

Ordene conforme a pergunta

Dois critérios, duas perspectivas

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

Troque o critério de ordenação

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.

Sequência de consulta

Coloque na ordem correta as ações de uma consulta por pstats.

  1. Carregar o arquivo com pstats.Stats
  2. Ordenar com sort_stats("cumulative") ou sort_stats("time")
  3. Exibir linhas com print_stats(...)

Mostre menos, investigue melhor

Restrinja a saída, não a execução passada

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.

Consulta por visão geral e por função

Execute as duas consultas sobre o mesmo arquivo de perfil.

python
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

Se o nome não aparecer

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.

Verifique sua leitura

Consulta local

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

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.

Duas medidas para a mesma chamada

Tempo próprio e tempo acumulado

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.

Chamadas aninhadas e tempos

Observe como o tempo da coordenadora inclui as funções que ela aciona.

Diagrama de uma chamada principal coordenando duas funções filhas, com 0,01 segundo próprio na coordenadora, 0,07 segundo em normalizar e 0,12 segundo em resumir, totalizando 0,20 segundo acumulado.

O tempo acumulado da coordenadora contém seu tempo próprio mais o trabalho das descendentes.

Um relatório numérico ilustrativo

Exemplo

Linhas do perfil

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

Use as duas ordenações

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.

Evite contar o mesmo tempo duas vezes

Associe a métrica à leitura correta

Faça as associações.

Toque em um item e depois no par correspondente.

Interprete sem somar linhas sobrepostas

Qual total representa essa execução?

Com as três linhas do exemplo, qual conclusão está correta sobre o tempo total do trecho?

Passo 5 de 9

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.

Contagem, custo por chamada e custo total

Leia as colunas em conjunto

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.

Dois padrões de custo

Compare custo por chamada, frequência e contribuição total.

Diagrama comparando poucas barras largas, que representam poucas chamadas caras, com muitas barras estreitas cuja soma também forma uma barra total grande, representando muitas chamadas baratas repetidas.

A contribuição para a carga depende da combinação entre o custo de cada chamada e a quantidade de repetições.

Exemplo

Duas linhas, duas suspeitas diferentes

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_palavra

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

Quando ncalls mostra dois números

Total/primitivas em funções recursivas

Em uma função recursiva, ncalls pode aparecer como total/primitivas, por exemplo, 8/3.

  • 8 é o total de chamadas registradas.
  • 3 é a quantidade de chamadas primitivas: chamadas que não foram provocadas por outra chamada recursiva da mesma função.

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

Tabela recursiva ilustrativa

ncalls  tottime  percall  cumtime  percall  função
   8/3    0.024    0.003    0.060    0.020  explorar_ramo

Cálculos aproximados:

  • Primeiro percall: 0,024 ÷ 8 = 0,003 s de tempo próprio por chamada total.
  • Segundo 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.

Verifique o denominador

Complete a leitura

Na linha 8/3, o segundo percall é calculado dividindo cumtime por ___.

Classifique a evidência

Qual é a melhor interpretação?

Considere estas linhas do perfil:

ncalls  tottime  percall  função
     5    0.250    0.050  funcao_a
50000    0.900    0.000  funcao_b

Qual afirmação é mais bem apoiada por esses dados?

Passo 6 de 9

Descobrir de onde vem o trabalho

Siga as relações de chamada do perfil para descobrir qual função provoca uma repetição observada.

Custo e origem não são a mesma coisa

Siga as setas do perfil

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.

Sentido das relações de chamada

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.

Diagrama de chamadas em que processar_registro chama normalizar_texto duas vezes para cada registro, enquanto montar_relatorio chama processar_registro.

O custo é registrado em normalizar_texto; a decisão de chamá-la duas vezes por registro está em processar_registro.

Dica

Não some relações sobrepostas

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.

Consultar chamadores e chamadas

Faça uma consulta direcionada

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.

consultar_chamadas.py

Consulta as relações de chamada no perfil salvo.

python
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

Como ler o resultado

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.

Escolha a consulta adequada

Chamadores ou chamadas?

Associe cada pergunta ao comando mais útil.

Toque em um item e depois no par correspondente.

Registre a evidência local

Onde investigar a frequência?

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

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.

O perfil aponta concentração, não uma conclusão final

Distribuição não é medição definitiva

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.

O que o cProfile revela

Leia o relatório como um mapa da carga executada.

Diagrama comparando uma execução instrumentada com cProfile, que registra funções e adiciona pequena sobrecarga, e uma medição sem instrumentação, usada para comparar duração total.

O perfil ajuda a localizar concentrações; a confirmação de ganho exige medir sem o cProfile ativo.

Atenção

Evite esta inferência

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

A fronteira do que aparece

Funções, não linhas nem todos os processos

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.

A fronteira da observação

O relatório mostra chamadas da execução perfilada, com detalhes até o nível de função Python.

Diagrama de uma função Python contendo várias linhas internas, uma chamada para recurso nativo com interior oculto e uma seta para processo separado fora da área observada.

Linhas internas, detalhes nativos e trabalho de outro processo não são explicados automaticamente pelo perfil atual.

Teste sua interpretação

Concentração observada

Se normalizar_texto concentra tempo ou chamadas no perfil, isso prova que alterar essa função melhorará o desempenho geral em qualquer carga.

Alcance do relatório

Qual conclusão é compatível com um relatório do cProfile de um único processo?

Passo 8 de 9

Transformar o diagnóstico em uma hipótese

Converta evidências do perfil em uma mudança pequena, verificável e segura.

Da evidência à hipótese

Uma hipótese não é um palpite

Use o perfil para formular uma explicação que possa falhar em um teste. Uma hipótese útil tem quatro partes:

  1. Evidência: o que apareceu no perfil, como muitas chamadas de uma função e seu contexto de chamada.
  2. Explicação possível: por que esse trabalho ocorre.
  3. Alteração proposta: uma mudança pequena e localizada.
  4. Resultado esperado: uma observação que poderá ser conferida depois.

O perfil indica onde investigar; ele não prova sozinho que uma alteração melhorará o programa.

Estrutura de uma hipótese verificável

Diagrama em quatro etapas: evidência observada no perfil leva a uma explicação possível, depois a uma pequena alteração no código e finalmente a uma validação por testes, novo perfil e medição externa.

A alteração deve derivar de uma evidência e produzir uma previsão observável.

Uma mudança localizada no cenário

Exemplo

Evitar normalização repetida no mesmo registro

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

Antes de reutilizar um resultado

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.

Defina o que confirmará ou refutará a ideia

Previsão e protocolo

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.

Organize a validação

Coloque o protocolo em uma ordem útil para investigar a hipótese.

  1. Executar testes de equivalência com a mesma carga e casos relevantes.
  2. Aplicar somente a alteração proposta.
  3. Medir as duas versões repetidamente com timeit, sem cProfile ativo.
  4. Gerar um novo perfil e conferir as chamadas previstas.
  5. Preservar a versão de referência e registrar a evidência inicial.

Registre sua hipótese

Escreva uma hipótese verificável

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

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.

Implemente as duas versões e preserve o resultado

Uma mudança verificável

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.

O que mudou no fluxo

Comparação visual: na versão de referência, um mesmo texto segue duas vezes para a função de normalização; na versão alterada, segue uma vez e o resultado normalizado é reutilizado para produzir o texto e a contagem de palavras.

A alteração reduz uma chamada redundante por registro, sem mudar o relatório produzido.

relatorio_validado.py

Execute este arquivo primeiro sem o perfilador para rodar as verificações e as medições.

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

Colete as evidências comparáveis

Primeiro, teste; depois, compare

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.

Comandos de execução e perfil

Rode os comandos no diretório em que você salvou o arquivo.

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

Consultar os dois perfis

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

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

Apresente sua decisão

Registro final de evidências

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

Feche o ciclo de otimização

Resumo

Checklist da conclusão

Um perfil aponta onde investigar; ele não prova sozinho que uma alteração melhorou o programa.

  • Mantenha uma versão de referência para comparar comportamento e desempenho.
  • Teste a equivalência em uma entrada representativa e em casos relevantes antes de aceitar a alteração.
  • Use um segundo perfil para verificar a previsão estrutural, como a redução de chamadas redundantes.
  • Compare as versões com timeit sem cProfile ativo, repetindo a mesma carga completa sob condições equivalentes.
  • Aceite, rejeite ou mantenha a hipótese como inconclusiva conforme correção e medições locais.

Investigação concluída

Parabéns! Você concluiu: Localizar gargalos de execução com cProfile

Você concluiu o ciclo: observou a distribuição de custo, formulou uma hipótese, preservou a correção e decidiu com medições independentes do perfilador. Use esse processo antes de otimizar por suposição.

Baixe o Aplicativo agora para ter acesso a + de 5000 cursos gratuitos, exercícios, certificado e muito conteúdo sem pagar nada!

  • Cursos online 100% gratuitos do início ao fim

    Milhares de cursos online em vídeo, ebooks e áudiobooks.

  • Mais de 60 mil exercícios gratuitos

    Para testar seus conhecimentos no decorrer dos cursos online

  • Certificado Digital gratuito válido em todo o Brasil

    Gerado diretamente na galeria de fotos do seu celular e enviado ao seu e-mail

Aplicativo Cursa na tela de ebook, na tela de curso em vídeo e na tela de exercícios do curso, mais o certificado de conclusão de curso