Introdução: Por que a Performance em Go é Importante
Go (Golang) é uma linguagem compilada estaticamente tipada, projetada para sistemas de alta carga. No entanto, mesmo em Go você pode escrever uma aplicação lenta se não considerar as especificidades da operação do garbage collector (GC), gerenciamento de memória e concorrência. Neste artigo, vamos detalhar técnicas-chave de otimização: desde profiling até o ajuste fino do GC. Você aprenderá como transformar código "cru" em uma solução performática pronta para produção.
1. Profiling: Encontrando Gargalos
O primeiro passo da otimização é o diagnóstico. Go fornece o pacote embutido net/http/pprof para coletar perfis de CPU, memória, locks e goroutines.
Iniciando o Profiling
Adicione ao main.go:
import ( _ "net/http/pprof" "net/http")
func main() { go func() { http.ListenAndServe("localhost:6060", nil) }() // seu código principal}Em seguida, execute:go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
Analisando o Perfil de CPU
Após coletar os dados, um console interativo será aberto. Use os comandos:
top10— mostra as 10 funções "mais quentes";web— visualiza o gráfico de chamadas no navegador;list <func_name>— mostra o uso de CPU linha por linha.
Exemplo de saída:
Showing nodes accounting for 80.60s, 85.12% of 94.70s total flat flat% sum% cum cum% 20.30s 21.44% 21.44% 20.30s 21.44% runtime.memmove 15.10s 15.94% 37.38% 15.10s 15.94% runtime.mallocgcSe você vir runtime.memmove ou mallocgc no topo — o problema são alocações frequentes de memória.
2. Gerenciamento de Memória e GC
O garbage collector em Go é "stop-the-world", mas desde o Go 1.5 ele se tornou paralelo. No entanto, alocações frequentes aumentam a carga no GC.
Evite Alocações Desnecessárias
Exemplo ruim:
func sum(values []int) int { total := 0 for _, v := range values { total += v } // o slice é passado por referência, mas se você criar uma cópia — isso é uma alocação return total}Exemplo bom — use canais com buffer e pools de objetos:
var bufferPool = sync.Pool{ New: func() interface{} { return make([]byte, 1024) },}
func process() { buf := bufferPool.Get().([]byte) defer bufferPool.Put(buf) // trabalhe com buf}Ajuste Fino do GC
A variável de ambiente GOGC controla a frequência da coleta. O padrão é 100 (a coleta começa quando o tamanho do heap dobra). Aumente para 200–500 para aplicações com muitas alocações:
export GOGC=200./myappMas tenha cuidado: um GOGC muito alto pode levar a um pico no consumo de memória.
3. Concorrência: Goroutines e Canais
Go é famosa por suas goroutines leves, mas o uso incorreto leva a vazamentos e deadlocks.
Limite o Número de Goroutines
Use um worker pool:
func workerPool(jobs []int, numWorkers int) { jobsChan := make(chan int, len(jobs)) results := make(chan int, len(jobs))
for w := 0; w < numWorkers; w++ { go func() { for job := range jobsChan { results <- processJob(job) // trabalho pesado } }() }
for _, j := range jobs { jobsChan <- j } close(jobsChan)
for i := 0; i < len(jobs); i++ { <-results }}Evite Bloqueios e Deadlocks
Use select com timeouts:
select {case res := <-resultChan: return rescase <-time.After(5 * time.Second): return nil