Seu código Go está lento? Hora de olhar sob o capô
\n\nVocê escreveu um serviço em Go. Ele funciona. Mas quando a carga atinge 10.000 RPS, o servidor começa a "engasgar". Situação familiar? Go é famoso por seu desempenho "de fábrica", mas até mesmo nesta linguagem elegante existem armadilhas. Vazamentos de goroutines, gerenciamento ineficiente de memória, alocações desnecessárias — tudo isso mata a velocidade.
\n\nNeste artigo, vamos explorar como o perfilamento Go ajuda a encontrar gargalos e mostrar técnicas específicas de otimização de aplicações Go. Você aprenderá como usar as ferramentas integradas pprof e go test -bench para transformar sua aplicação em um foguete. Vamos nessa!
\n\nKit de ferramentas do profiler: pprof e trace
\n\nO Go fornece duas ferramentas poderosas para análise de desempenho: pprof (perfilamento de CPU, memória, bloqueios) e go tool trace (análise de concorrência e eventos GC).
Incorporando pprof no servidor HTTP
\n\nA maneira mais simples de começar o perfilamento é adicionar net/http/pprof ao seu servidor. Isso leva exatamente uma linha de importação:
import (\n _ "net/http/pprof"\n "net/http"\n)\n\nfunc main() {\n http.ListenAndServe(":8080", nil)\n}\n\nAgora, os perfis estão disponíveis em /debug/pprof/. Para capturar um perfil de CPU por 30 segundos, execute:
go tool pprof http://localhost:8080/debug/pprof/profile?seconds=30\n\nApós a coleta dos dados, um console interativo será iniciado. O comando top15 mostrará as funções mais quentes:
(pprof) top15\nShowing nodes accounting for 3.50s, 87.50% of 4.00s total\n flat flat% sum% cum cum%\n 1.20s 30.00% 30.00% 1.50s 37.50% main.heavyComputation\n\nViu o main.heavyComputation? Este é nosso candidato para otimização. Para visualização, use web (se o Graphviz estiver instalado) — você obterá um grafo de chamadas.
Perfilamento de memória: procurando vazamentos
\n\nVazamentos de memória em Go geralmente são vazamentos de goroutines ou referências esquecidas a objetos grandes. Vamos capturar um perfil heap:
\n\ngo tool pprof http://localhost:8080/debug/pprof/heap\n\nO comando alloc_objects mostrará quais funções criam mais objetos:
(pprof) alloc_objects\n 2.50GB 100% 100% 2.50GB 100% main.createLargeSlice\n\nFoi assim que encontramos uma função que aloca 2,5 GB de memória! Talvez ela esteja criando um slice enorme que pode ser reutilizado com sync.Pool.
Escrevendo benchmarks: seu primeiro passo para otimização
\n\nAntes de otimizar, é preciso medir. Os benchmarks Go são a ferramenta padrão para medir o desempenho de funções individuais.
\n\nExemplo: comparando concatenação de strings
\n\nSuponha que estamos concatenando strings em um loop. Versão ruim:
\n\nfunc ConcatSlow(parts []string) string {\n result := ""\n for _, p := range parts {\n result += p\n }\n return result\n}\n\nVersão boa — usar strings.Builder:
func ConcatFast(parts []string) string {\n var b strings.Builder\n for _, p := range parts {\n b.WriteString(p)\n }\n return b.String()\n}\n\nVamos escrever o benchmark (arquivo concat_test.go):
func BenchmarkConcatSlow(b *testing.B) {\n parts := []string{"Hello", " ", "World", "!", " ", "From", " ", "Go"}\n for i := 0; i < b.N; i++ {\n ConcatSlow(parts)\n }\n}\n\nfunc BenchmarkConcatFast(b *testing.B) {\n parts := []string{"Hello", " ", "World", "!", " ", "From", " ", "Go"}\n for i := 0; i < b.N; i++ {\n ConcatFast(parts)\n }\n}\n\nExecutamos:
\n\ngo test -bench=. -benchmem\n\nResultado:
\n\nBenchmarkConc