Profiling com pprof
1. Introdução ao Profiling em Go
Profiling é o processo de medição dinâmica do comportamento de um programa durante sua execução, coletando dados sobre uso de CPU, memória, concorrência e outros recursos. Em Go, o profiling é essencial para identificar gargalos de performance, vazamentos de memória e problemas de concorrência antes que eles afetem usuários em produção.
A ferramenta nativa pprof (profiling profiler) está integrada ao ecossistema Go através dos pacotes runtime/pprof e net/http/pprof. Ela permite coletar cinco tipos principais de perfis:
- CPU: onde o programa gasta tempo de processamento
- Memória (heap): alocações e retenção de objetos
- Goroutine: número e estado de goroutines ativas
- Block: operações que bloqueiam goroutines
- Mutex: contenção em locks (mutexes)
Cada perfil responde a uma pergunta específica sobre o comportamento do programa, permitindo otimizações direcionadas.
2. Habilitando e Coletando Perfis
Para aplicações web
O pacote net/http/pprof expõe endpoints HTTP para coleta remota de perfis. Basta importá-lo silenciosamente:
package main
import (
"log"
"net/http"
_ "net/http/pprof" // registra handlers no DefaultServeMux
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, profiling!"))
})
log.Println("Servidor rodando em :8080 - pprof em /debug/pprof/")
log.Fatal(http.ListenAndServe(":8080", nil))
}
Com o servidor rodando, colete perfis com:
# Perfil de CPU por 30 segundos
go tool pprof http://localhost:8080/debug/pprof/profile?seconds=30
# Perfil de heap
go tool pprof http://localhost:8080/debug/pprof/heap
# Perfil de goroutines
go tool pprof http://localhost:8080/debug/pprof/goroutine
Para programas CLI
Use runtime/pprof para salvar perfis em arquivos:
package main
import (
"os"
"runtime/pprof"
)
func main() {
// CPU profiling
fCPU, _ := os.Create("cpu.pprof")
pprof.StartCPUProfile(fCPU)
defer pprof.StopCPUProfile()
// Executa o trabalho pesado
doWork()
// Memória profiling
fMem, _ := os.Create("mem.pprof")
pprof.WriteHeapProfile(fMem)
defer fMem.Close()
}
func doWork() {
// Simula processamento intensivo
sum := 0
for i := 0; i < 100000000; i++ {
sum += i
}
}
3. Análise de Perfil de CPU
Para iniciar o CPU profiling, chamamos pprof.StartCPUProfile() e paramos com pprof.StopCPUProfile(). O perfil captura amostras periódicas do contador de programa.
Analise o perfil com go tool pprof:
go tool pprof cpu.pprof
Dentro do shell interativo do pprof, comandos úteis:
(pprof) top10 # Top 10 funções mais custosas
(pprof) top --cum # Ordena por tempo cumulativo
(pprof) list minhasFuncao # Mostra o código com tempos por linha
(pprof) web # Gera gráfico SVG (requer Graphviz)
(pprof) peek # Mostra callers e callees de uma função
Exemplo de saída top:
Showing nodes accounting for 80.50s, 85.11% of 94.58s total
Dropped 42 nodes (cum <= 0.47s)
flat flat% sum% cum cum%
30.20s 31.93% 31.93% 30.20s 31.93% regexp.(*input).step
25.40s 26.85% 58.78% 25.40s 26.85% runtime.memmove
15.10s 15.96% 74.74% 45.30s 47.89% regexp.(*machine).add
9.80s 10.36% 85.10% 9.80s 10.36% runtime.memclrNoHeapPointers
A coluna flat mostra o tempo gasto diretamente na função, enquanto cum inclui chamadas internas. Funções com alta taxa flat são candidatas imediatas a otimização.
4. Análise de Perfil de Memória
O perfil de heap mostra alocações ativas. Os principais modos de visualização são:
inuse_space: quantidade de memória alocada e não liberadainuse_objects: número de objetos alocados e não liberadosalloc_space: total de memória alocada desde o inícioalloc_objects: total de objetos alocados
Para analisar:
go tool pprof -alloc_space mem.pprof
go tool pprof -inuse_objects mem.pprof
Identificando vazamentos
Execute o programa duas vezes (antes e depois de um intervalo) e compare:
# Primeira coleta
curl -o mem1.pprof http://localhost:8080/debug/pprof/heap
# Após algum tempo
curl -o mem2.pprof http://localhost:8080/debug/pprof/heap
# Comparação
go tool pprof -base mem1.pprof mem2.pprof
O comando -base mostra o delta entre os perfis. Funções que crescem consistentemente indicam vazamento.
5. Perfis de Concorrência: Goroutine, Block e Mutex
Perfil de goroutines
Revela goroutines que não estão progredindo:
# Lista todas as goroutines com suas pilhas
go tool pprof http://localhost:8080/debug/pprof/goroutine?debug=2
No modo interativo, use top para ver funções que criam muitas goroutines.
Perfil de block
Mostra operações que bloqueiam goroutines (channels, mutexes, I/O). Habilite antes:
runtime.SetBlockProfileRate(1) // 1 = amostrar todas as operações
Colete:
go tool pprof http://localhost:8080/debug/pprof/block
Perfil de mutex
Revela contenção em mutexes. Habilite com:
runtime.SetMutexProfileFraction(1) // 1 = amostrar todos os conflitos
Trace para visualização avançada
O pacote runtime/trace gera arquivos que podem ser abertos no navegador:
import "runtime/trace"
f, _ := os.Create("trace.out")
trace.Start(f)
defer trace.Stop()
Visualize com:
go tool trace trace.out
6. Visualização e Ferramentas Avançadas
Gráficos com Graphviz
Instale o Graphviz e use:
go tool pprof -svg cpu.pprof > cpu.svg
go tool pprof -pdf cpu.pprof > cpu.pdf
Interface web interativa
Desde Go 1.11, o pprof tem servidor HTTP próprio:
go tool pprof -http=:8081 cpu.pprof
Isso abre uma interface web com flame graphs, gráficos de chama e visualização interativa do call graph.
Flame graphs
No servidor web do pprof, acesse a aba "Flame Graph" para ver uma representação hierárquica do uso de recursos. Cada retângulo representa uma função, com largura proporcional ao tempo consumido.
7. Boas Práticas e Armadilhas Comuns
Impacto na performance
Profiling adiciona overhead. CPU profiling reduz a taxa de execução em 5-10%. Use amostragem (padrão de 100 amostras/segundo) e evite profiling contínuo em produção.
Profiling em produção
- Nunca exponha
/debug/pprof/publicamente sem autenticação - Use profiling apenas em ambientes de staging ou com tráfego controlado
- Considere profiling amostral em produção com
pprof.StartCPUProfilepor curtos períodos
Limpeza entre execuções
Sempre feche arquivos de perfil e chame StopCPUProfile():
func collectProfile() {
f, _ := os.Create("cpu.pprof")
pprof.StartCPUProfile(f)
defer func() {
pprof.StopCPUProfile()
f.Close()
}()
// trabalho...
}
Armadilhas comuns
- Perfil de memória sem GC: execute
runtime.GC()antes de coletar para ver apenas objetos vivos - Amostragem insuficiente: colete por pelo menos 30 segundos para CPU
- Comparações inválidas: sempre use a mesma duração e carga de trabalho ao comparar perfis
Referências
- Documentação oficial do pacote pprof — Referência completa dos endpoints HTTP e parâmetros do pprof
- Profiling Go Programs - Blog oficial da Go Team — Artigo clássico explicando conceitos fundamentais de profiling em Go
- pprof: Go's Profiling Tool - Dave Cheney — Guia prático com exemplos de uso do pprof em cenários reais
- Using pprof to analyze Go programs - Julia Evans — Tutorial acessível com dicas de interpretação de perfis
- Flame Graphs in Go - Brendan Gregg — Técnica de visualização de perfis adotada pelo pprof, com explicações detalhadas
- runtime/pprof documentation — Documentação oficial do pacote para profiling em programas CLI
- High Performance Go - Dave Cheney — Workshop completo sobre otimização de performance em Go, incluindo profiling avançado