Módulo 14 - Performance e profiling

Achar o gargalo com cProfile e pstats

10 min de leitura · por Cesar Gargiulo, revisado pela equipe ValorFinal e GuardiaSec · Atualizado em 01/07/2026

O que você vai aprender

  • Rodar o cProfile sobre uma função ou programa inteiro.
  • Ler as colunas do relatório: ncalls, tottime e cumtime.
  • Ordenar o resultado com pstats para achar o gargalo.
  • Distinguir tempo próprio da função de tempo acumulado.

Perfilando o programa inteiro

Enquanto o timeit responde qual trecho é mais rápido, o cProfile responde onde meu programa gasta tempo. Ele é um profiler determinístico: acompanha cada entrada e saída de função durante a execução real e monta uma tabela com quantas vezes cada função foi chamada e quanto tempo consumiu. É a foto exata de onde o tempo foi parar, sem palpite. A forma mais simples de usar é chamar cProfile.run com a expressão que você quer perfilar.

import cProfile

def eh_primo(n):
    if n < 2:
        return False
    for d in range(2, int(n ** 0.5) + 1):
        if n % d == 0:
            return False
    return True

def contar_primos(limite):
    return sum(1 for n in range(limite) if eh_primo(n))

cProfile.run("contar_primos(100000)")

cProfile.run perfila a expressão e imprime uma tabela com todas as funções chamadas.

O relatório impresso tem uma linha por função, com colunas que valem entender. A coluna ncalls diz quantas vezes a função foi chamada. A coluna tottime mostra o tempo gasto apenas no corpo daquela função, sem contar o que ela chama por dentro. A coluna cumtime mostra o tempo cumulativo, incluindo tudo que a função aciona. No exemplo, contar_primos tem cumtime alto, porque engloba todas as chamadas a eh_primo, mas tottime baixo, porque o trabalho pesado mesmo acontece dentro de eh_primo.

Ordenando o relatório com pstats

Em programas reais, a tabela do profiler tem dezenas ou centenas de linhas, e ler tudo é inviável. O módulo pstats resolve isso: ele carrega o resultado do profiler e deixa você ordenar e filtrar. O passo prático é salvar o perfil em um arquivo e depois abrir com pstats, pedindo a ordenação por tempo. Ordenar por tottime coloca no topo as funções onde o trabalho de fato acontece; ordenar por cumtime destaca as funções que, direta ou indiretamente, dominam a execução.

import cProfile
import pstats

cProfile.run("contar_primos(100000)", "perfil.out")

p = pstats.Stats("perfil.out")
p.sort_stats("tottime")   # ordena pelo tempo proprio de cada funcao
p.print_stats(5)          # mostra so as 5 funcoes mais custosas

pstats carrega o perfil salvo, ordena por tempo próprio e mostra só o topo da lista.

ColunaSignificaQuando olhar
ncallsNúmero de chamadas da funçãoSuspeita de excesso de chamadas
tottimeTempo só no corpo da funçãoAchar onde o trabalho pesado roda
cumtimeTempo da função mais o que ela chamaAchar a origem de uma cadeia lenta
percallTempo médio por chamadaComparar custo unitário entre funções

As colunas do relatório do profiler e para que serve cada uma.

Do perfil à decisão de otimizar

O perfil não otimiza nada sozinho; ele aponta onde vale investir. A regra prática é a lei do desequilíbrio: quase sempre uma fração pequena do código responde pela maior parte do tempo. Encontrar essa fração é o objetivo. Depois de identificar a função campeã, você decide a estratégia: talvez ela seja chamada vezes demais e a solução seja evitar chamadas, com memoização; talvez ela use uma estrutura de dados ruim para a busca que faz; talvez o algoritmo em si tenha uma complexidade alta. As próximas aulas dão essas ferramentas.

Um detalhe honesto: o cProfile adiciona uma sobrecarga, porque acompanha cada chamada. Os tempos absolutos ficam maiores do que na execução normal, então não os use como medida final de velocidade. O que importa no perfil é a proporção entre as funções, ou seja, quem consome mais em relação às outras. Para o número final de uma otimização específica, volte ao timeit ou meça o programa real sem o profiler ativo. O perfil aponta o alvo; a medição limpa confirma o ganho.

Teste rápido

No relatório do cProfile, qual a diferença entre tottime e cumtime?

Perguntas frequentes

Quando usar cProfile em vez de timeit?
Use o cProfile quando quiser descobrir onde um programa inteiro gasta tempo, função por função. Use o timeit quando já souber qual trecho quer afinar e precisar comparar duas versões dele com precisão. Na prática, o cProfile aponta o gargalo e o timeit mede a alternativa.
Por que os tempos do cProfile parecem maiores que o normal?
Porque o profiler acompanha cada chamada de função, e isso adiciona sobrecarga. Os tempos absolutos ficam inflados. O que importa no perfil é a proporção entre as funções, não o número final. Para medir o ganho real de uma otimização, use uma medição limpa, sem o profiler ativo.
Como salvo o perfil para analisar depois?
Passe um nome de arquivo como segundo argumento para cProfile.run, por exemplo cProfile.run('funcao()', 'perfil.out'). Depois abra esse arquivo com pstats.Stats('perfil.out') e use sort_stats e print_stats para ordenar e ver só as funções mais custosas.
Ordeno por tottime ou por cumtime?
Comece por tottime, que coloca no topo as funções onde o trabalho bruto acontece. Use cumtime para subir a cadeia de chamadas e descobrir qual função de alto nível está, no fundo, disparando todo esse trabalho. Os dois olhares se complementam.
O cProfile serve para código com concorrência ou async?
Ele funciona, mas a leitura fica mais delicada, porque tempo esperando entrada e saída aparece misturado com tempo de CPU. Para código assíncrono, foque no tempo de CPU das funções e lembre que a espera por rede ou disco não é gargalo de processamento. Ferramentas específicas de perfil ajudam nesses casos.

Fontes

Seu progresso fica salvo neste aparelho. Assinantes sincronizam entre os aparelhos.