DevGuideTK

Profiling

Como medir onde o tempo e a memória são gastos antes de otimizar.

O problema: a intuição erra o gargalo

Um relatório está lento. A equipe olha o código e aposta: "é a formatação dos valores", "é o JSON", "é o banco". Cada aposta vira uma otimização, e o relatório continua lento, porque o tempo estava em outro lugar.

Profiling é medir, com o programa rodando, onde o tempo e a memória vão parar, função por função. A ferramenta é o profiler, e toda linguagem tem pelo menos um.

Um gargalo escondido, medido em cinco linguagens

Escrevemos o mesmo relatório em TypeScript, Python, Go, Java e C#: 100 mil pedidos, os clientes únicos (20 mil), o total de cada um e uma linha formatada por cliente. A versão lenta tem um erro comum: para saber se o cliente já está na lista, percorre a lista inteira (includes, not in, contains). Com 20 mil clientes, são bilhões de comparações. A versão corrigida usa um conjunto (Set, dict, map, HashSet), que responde em tempo constante.

Versão lentaVersão corrigida
TypeScript (Node 24)894 ms28 ms
Python 3.1311.374 ms30 ms
Go 1.25286 ms12 ms
Java 211.011 ms177 ms
C# (.NET 9)172 ms10 ms

De 6 a 380 vezes mais lento, por uma linha. Um código com esse erro passa em qualquer teste com poucos dados e só aparece em produção, com volume.

O que cada profiler mostrou

Rodamos a versão lenta sob o profiler de cada linguagem:

LinguagemProfilerO que apareceu no topo
Gopprofslices.Index, 92,6% do tempo próprio, chamado por clientesUnicosLento
PythoncProfileclientes_unicos_lento, 11,4 s dos 11,5 s totais
JavaJDK Flight RecorderArrayList.indexOfRange e Integer.equals, o código de dentro do contains
C#dotnet-traceSpanHelpers.NonPackedIndexOfValueType, 74,9%, o código de dentro do List.Contains
TypeScriptnode --cpu-profrelatorio, 88,4%

Os cinco apontaram o lugar certo, cada um do seu jeito. Duas lições estão na tabela:

  • Tempo próprio e tempo acumulado. Em Java e C#, o topo da lista era uma função da biblioteca padrão, onde o processador de fato estava. Para chegar ao código da aplicação, é preciso olhar quem a chamou: o tempo acumulado (cumulative, inclusive) de clientesUnicosLento inclui o tempo das funções que ela chama. O pprof do Go mostrou as duas colunas lado a lado.
  • Funções embutidas pelo compilador. No Node, o tempo foi atribuído a relatorio, e não à função lenta: o compilador do V8 embutiu (inlining) a função lenta dentro da que a chamava. O profiler mostra o código como ele roda, não como foi escrito.

Amostragem e instrumentação

Os profilers funcionam de dois jeitos:

Por amostragemPor instrumentação
Como medeOlha a pilha de chamadas centenas de vezes por segundo e conta onde o programa estavaRegistra a entrada e a saída de cada chamada de função
Exemplospprof, JFR, dotnet-trace, node --cpu-prof, py-spycProfile
Peso no programaBaixoProporcional ao número de chamadas
PrecisãoEstatística: funções muito rápidas podem não aparecerConta cada chamada, com o número de chamadas
Em produçãoViável, com cuidadoRaramente

Medir sempre pesa. No teste de memória, o tracemalloc do Python, que rastreia cada alocação, deixou a versão corrigida do relatório mais de quatro vezes mais lenta: 132 ms, contra 30 ms sem ele. Os números absolutos de um programa sob profiler não valem como medida de desempenho; o que vale é a proporção entre as partes.

Memória

O tempo é metade da história. A outra é onde a memória é alocada e quem a segura. O mesmo relatório, sob o tracemalloc:

LinhaMemória
A geração dos 100 mil pedidos, como tuplas11,1 MB
As 20 mil linhas formatadas1,3 MB
A lista final0,2 MB

O profiler de memória responde perguntas como "por que o processo usa 2 GB?" e "o que cresce sem parar?" (um vazamento). Em linguagens com coletor de lixo, ele mostra também quanto o programa aloca por segundo, que pesa no tempo gasto coletando, como mostra o tópico de gerenciamento de memória.

Gráficos de chama

A forma mais útil de ler um perfil é o gráfico de chama (flame graph): cada barra é uma função, a largura é a fração do tempo, e as barras de cima são chamadas pelas de baixo. O gargalo aparece como a barra mais larga no alto da pilha, e o caminho até ele fica visível abaixo. O go tool pprof -http, o Speedscope, o painel de desempenho do Chrome (para os perfis do Node) e o JDK Mission Control desenham esses gráficos.

Por que isso importa quando a IA escreve o código

Um assistente que otimiza código costuma otimizar sem medir:

  • Microotimizações em trechos que não pesam, deixando o gargalo de verdade intocado.
  • Buscas em listas dentro de laços (includes, in, contains), O(n²) escondido.
  • Conclusões tiradas de tempos medidos com o profiler ligado.
  • Leitura do tempo próprio sem olhar o acumulado, otimizando a biblioteca padrão em vez do código que a chama.
  • Cache acrescentado para esconder uma consulta ou um laço lento, sem entender a causa.

O próximo nível compara os profilers de cada linguagem, quando usar cada tipo e como medir em produção.

Prompts para aprofundar

Ao abrir no ChatGPT ou no Claude, o prompt é enviado ao serviço escolhido pela URL e também fica copiado na sua área de transferência.

  • Profiling em cinco minutos

    Explique em no máximo cinco minutos de leitura o que é profiling, para alguém que sempre otimizou código por intuição.
    
    Cubra só o essencial: o que um profiler mede, amostragem e instrumentação, tempo próprio e acumulado, gráficos de chama e por que medir pesa no programa.
    
    Use o exemplo de um relatório lento. No final, me faça três perguntas curtas para eu verificar se entendi, e corrija as minhas respostas.
  • Lendo um perfil de CPU

    Quero aprender a ler o resultado de um profiler de CPU.
    
    Aja como um mentor e vá por etapas, esperando a minha resposta em cada uma:
    1. Me mostre a saída de um profiler com tempo próprio e acumulado e peça que eu ache o gargalo.
    2. Explique por que a função no topo pode ser da biblioteca padrão, e como subir até o código da aplicação.
    3. Me descreva um gráfico de chama e peça que eu diga onde está o problema.
    4. Explique o efeito de funções embutidas pelo compilador no perfil.
    5. Mostre um perfil de um processo que está esperando o banco, e peça que eu diga o que ele não mostra.
  • Perfis de memória

    Quero entender como investigar o uso de memória de uma aplicação.
    
    Explique:
    1. A diferença entre memória alocada, memória retida e memória do processo.
    2. Instantâneos do heap e como compará-los para achar um vazamento.
    3. Perfis de alocação e a relação com o coletor de lixo.
    4. As ferramentas da minha linguagem (pergunte qual é).
    5. Os vazamentos mais comuns em aplicações web.
    
    Depois me descreva um sintoma e pergunte como eu investigaria.
  • Por que a intuição erra o gargalo

    Quero treinar a intuição sobre onde o tempo de um programa vai parar, e entender por que ela erra.
    
    Me mostre, uma de cada vez, cinco trechos de código com algum problema de desempenho escondido (busca linear em laço, consulta em laço, serialização repetida, expressão regular compilada a cada chamada, cópia de coleção grande). Para cada um, peça que eu aponte o problema e estime o impacto, e depois explique o que um profiler mostraria.