Introdução: por que o perfilamento Go é essencial para o desenvolvedor
\nGo é conhecido por seu desempenho, mas até o código mais rápido pode ficar lento devido a algoritmos ineficientes, vazamentos de memória ou mau uso de goroutines. O perfilamento é a única maneira de saber exatamente onde seu aplicativo está gastando recursos. Neste artigo, abordaremos métodos práticos de perfilamento de código Go usando ferramentas integradas: pprof, trace e benchstat. Você aprenderá a encontrar gargalos e eliminá-los.
1. Perfilamento de CPU com pprof: encontrando pontos críticos
\nO perfilamento de CPU mostra quais funções consomem mais tempo de processamento. O pacote integrado runtime/pprof permite coletar perfis facilmente.
1.1. Adicionando perfilamento ao código
\nVamos considerar um exemplo de um servidor HTTP simples que executa tarefas pesadas:
\npackage main\n\nimport (\n "fmt"\n "log"\n "net/http"\n _ "net/http/pprof"\n "os"\n "runtime/pprof"\n)\n\nfunc heavyTask() {\n sum := 0\n for i := 0; i < 1_000_000; i++ {\n sum += i * i\n }\n _ = sum\n}\n\nfunc handler(w http.ResponseWriter, r *http.Request) {\n heavyTask()\n fmt.Fprintf(w, "Done")\n}\n\nfunc main() {\n f, _ := os.Create("cpu.prof")\n pprof.StartCPUProfile(f)\n defer pprof.StopCPUProfile()\n\n http.HandleFunc("/", handler)\n log.Fatal(http.ListenAndServe(":8080", nil))\n}\n\nApós iniciar o servidor e fazer algumas requisições, você terá o arquivo cpu.prof. Analise-o com o comando:
go tool pprof -http=:8081 cpu.prof\nUm gráfico interativo será aberto no navegador. Observe os nós vermelhos — eles indicam as funções mais "quentes". No nosso caso, é heavyTask.
1.2. Interpretando os resultados
\nNa interface web do pprof, você verá:
\n- \n
- Graph — grafo de chamadas com tamanhos de nós proporcionais ao tempo de execução. \n
- Flame Graph — gráfico "chama", onde barras longas representam as seções mais lentas. \n
- Top — tabela de funções ordenadas por tempo de CPU. \n
Se você perceber que heavyTask está ocupando 90% do tempo, significa que é necessário otimizá-la (por exemplo, substituir o loop por uma fórmula de soma de quadrados).
2. Perfilamento de memória: encontrando vazamentos e alocações
\nVazamentos de memória em Go são raros, mas alocações excessivas podem desacelerar muito o aplicativo. Usamos runtime/pprof para o perfil de heap.
2.1. Coletando o perfil de heap
\nVamos adicionar ao código a coleta do perfil de memória via sinal SIGUSR1:
\nimport (\n "os"\n "os/signal"\n "runtime/pprof"\n "syscall"\n)\n\nfunc main() {\n // ... seu código ...\n sigs := make(chan os.Signal, 1)\n signal.Notify(sigs, syscall.SIGUSR1)\n go func() {\n <-sigs\n f, _ := os.Create("mem.prof")\n pprof.WriteHeapProfile(f)\n f.Close()\n os.Exit(0)\n }()\n // ...\n}\n\nApós enviar o sinal kill -SIGUSR1 <PID>, você obterá o arquivo mem.prof. Analise:
go tool pprof -http=:8082 mem.prof\n\n2.2. Problemas típicos de memória
\n- \n
- Grande quantidade de pequenas alocações — por exemplo, criar strings com
+em vez destrings.Builder. \n - Manter referências a objetos grandes — após o uso, é necessário zerar os ponteiros. \n
- Goroutines que não terminam — cada goroutine consome pelo menos 4 KB de pilha. \n
Exemplo de código não otimizado:
\nfunc badConcat(n int) string {\n s := ""\n for i := 0; i < n; i++ {\n s += "x" // cada iteração cria uma nova string\n }\n return s\n}\n\nOtimiz