Otimização sem medição costuma atacar o trecho errado. cProfile, incluído no Python, registra chamadas e tempos de funções para mostrar onde uma carga realmente consome recursos.
Definir uma pergunta reproduzível
O perfil deve responder algo concreto, como “por que importar 10 mil linhas passa de dois segundos?”. Registre versão do Python, dependências, entrada, máquina e comando. Desative trabalho alheio e execute mais de uma vez. Uma entrada artificial pequena pode destacar inicialização que quase não pesa no uso real.
Gerar e ler um perfil
python -m cProfile -o perfil.prof app.py
python -m pstats perfil.prof
No console do pstats:
sort cumulative
stats 20
tottime mostra tempo no corpo da função, excluindo chamadas internas. cumtime inclui as funções chamadas e ajuda a identificar caminhos caros. Salvar o arquivo permite comparar e investigar sem repetir a execução imediatamente.
ncalls pode aparecer como chamadas primitivas e totais em recursão. percall divide o tempo pela contagem. Uma função barata pode dominar por executar milhões de vezes; outra lenta pode estar apenas aguardando serviço externo.
Também é possível medir um bloco controlado:
import cProfile
import pstats
with cProfile.Profile() as profiler:
executar_carga()
pstats.Stats(profiler).sort_stats("cumulative").print_stats(20)
Filtrar e interpretar
stats = pstats.Stats("perfil.prof")
stats.strip_dirs()
stats.sort_stats("cumulative")
stats.print_stats("meuapp/", 25)
stats.print_callers(10)
stats.print_callees(10)
print_stats() aceita filtros de texto e quantidade. print_callers() mostra quem chama uma função cara; print_callees() mostra para onde ela delega. Ordenar por tottime expõe corpos caros, enquanto cumulative revela caminhos caros. Examine ambos. strip_dirs() facilita a leitura, mas pode fundir nomes iguais após remover diretórios.
Salve arquivos distintos para execuções repetidas. Só some perfis com cargas realmente comparáveis. Na comparação antes e depois, mantenha entrada e ambiente, use também um benchmark fora do profiler e reporte variação, não apenas a melhor execução.
Medir somente a fase relevante
python -m cProfile -o importacao.prof -s cumulative -m meuapp.importador amostra.csv
O uso programático deixa a preparação fora da região:
registros = carregar_fixture("amostra.json")
profiler = cProfile.Profile()
profiler.enable()
resultado = transformar(registros)
profiler.disable()
profiler.dump_stats("transformacao.prof")
Aqueça caches somente se produção normalmente os encontra aquecidos. Se inicialização fria importa, meça separadamente. Não imprima resultados grandes dentro da região, pois o terminal pode dominar o perfil.
Distinguir CPU de espera
cProfile é determinístico: observa eventos de chamadas Python e atribui tempo decorrido às funções. Isso adiciona overhead, sobretudo com muitas chamadas pequenas. Use-o para encontrar candidatos e valide o ganho com benchmark sem profiler.
Tempo numa função de banco ou HTTP pode representar espera, não CPU. Otimize consulta, lote, protocolo ou concorrência em vez do pequeno wrapper Python. Extensões nativas podem aparecer como poucas chamadas mesmo realizando bastante trabalho. Para linha específica, memória, escalonamento assíncrono ou amostragem em produção, escolha ferramenta própria.
Repetir um ciclo disciplinado
Comece pela maior entrada relevante que seu código pode mudar com segurança. Formule uma hipótese, faça uma alteração focada, rode testes, benchmark e perfil novamente. Exemplos: trocar buscas lineares repetidas por um conjunto, mover parsing invariável para fora do loop, agrupar consultas ou remover serialização redundante.
def ids_unicos_lento(linhas):
resultado = []
for linha in linhas:
if linha["id"] not in resultado:
resultado.append(linha["id"])
return resultado
def ids_unicos(linhas):
return list(dict.fromkeys(linha["id"] for linha in linhas))
A segunda versão melhora buscas preservando a ordem da primeira ocorrência, mas uma medição com linhas representativas precisa confirmar que a função importa. Menos chamadas não significa sempre mais velocidade, e memoização troca memória e atualidade por tempo.
Proteger os dados
Arquivos de perfil revelam caminhos, módulos, arquitetura e nomes de funções. Mantenha-os fora de diretórios públicos e não versione capturas improvisadas de produção. Use entradas sanitizadas e armazenamento com acesso controlado.
Usar uma carga representativa
Prepare entrada próxima do uso real e aqueça caches quando isso fizer parte do cenário. Separe CPU, espera de rede e banco de dados. Um profiler de chamadas não explica sozinho consultas lentas ou contenção externa.
Verificações automáticas podem detectar regressões grandes, mas limites generosos são melhores que asserções frágeis de tempo. Acompanhe a distribuição de benchmarks em máquinas comparáveis e investigue com perfil quando ela mudar.
Evitar erros de interpretação
Não otimize a primeira linha apenas porque ela aparece no topo; confirme que o tempo afeta o objetivo percebido pelo usuário. Imports podem dominar um comando curto e desaparecer num serviço persistente. Fixtures e preparação de teste podem dominar se ficarem dentro da região medida. Em recursão, leia com cuidado chamadas primitivas e totais.
Melhora no relógio real é o critério final para latência. CPU, memória, throughput e cauda de latência podem mudar em direções diferentes, portanto declare qual métrica está sendo melhorada. Uma alteração que acelera a mediana e piora as requisições mais lentas pode ser regressão para uma API.
Mantenha um registro curto com comando, checksum da entrada, ambiente, baseline, alteração, resultado e decisão. Isso explica otimizações incomuns para quem mantiver o código e torna experimentos rejeitados úteis.
Ao analisar uma aplicação concorrente, isole uma requisição ou unidade de trabalho sempre que possível. Um perfil agregado pode misturar caminhos rápidos e lentos, ocultando a causa. Para aplicações assíncronas, tempo atribuído a uma coroutine pode incluir períodos em que ela cedeu controle, conforme o ponto observado. Relacione o perfil a traces, métricas do banco e logs de duração antes de concluir que existe consumo de CPU.
Resultados devem orientar prioridades, não substituir julgamento. Considere frequência, impacto para usuários, risco da mudança e facilidade de manutenção. Um trecho que consome 40% de uma rotina executada uma vez por mês pode ser menos importante que uma função menor presente em toda requisição. Depois de obter ganho confirmado, mantenha um benchmark pequeno e documentado para detectar regressão futura.
Otimize o maior gargalo que possa ser alterado com segurança, depois execute testes e meça novamente. Evite sacrificar clareza por uma diferença pequena e instável. O guia de otimização em Python reúne outras técnicas.
A documentação oficial dos profilers Python, consultada em 22 de julho de 2026, recomenda cProfile para a maioria dos usuários por seu overhead menor. Preserve o cenário e os números para que a decisão seja reproduzível.