Introdução
Go (Golang) é uma linguagem compilada e estaticamente tipada, criada pelo Google para o desenvolvimento de aplicações server-side de alto desempenho. No entanto, mesmo em uma linguagem tão eficiente, podemos encontrar gargalos que reduzem a velocidade e consomem recursos extras. Neste artigo, veremos os principais métodos de otimização de desempenho para aplicações Go: desde profiling até gerenciamento de memória e concorrência. Você aprenderá como identificar problemas, corrigi-los e escrever código que seja rápido e estável.
1. Profiling: encontrando gargalos
Antes de otimizar, é preciso entender o que está desacelerando a aplicação. Em Go, existe uma ferramenta embutida para isso — o pprof. Ele permite coletar perfis de CPU, memória, goroutines e bloqueios.
Como iniciar o profiling
Adicione o import net/http/pprof ao código e inicie um servidor HTTP:
package main
import (
"log"
"net/http"
_ "net/http/pprof"
)
func main() {
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
// seu código principal
select {}
}
Em seguida, execute o comando no terminal:
go tool pprof -http=:8080 http://localhost:6060/debug/pprof/profile?seconds=30
Isso abrirá uma interface web onde são mostrados claramente os hotspots — funções que consomem mais CPU. Analise-os e otimize-os primeiro.
Benchmarking para medições precisas
Para testes isolados, use testing.B. Crie um arquivo example_test.go:
package main
import "testing"
func BenchmarkMyFunction(b *testing.B) {
for i := 0; i < b.N; i++ {
MyFunction()
}
}
Execute o benchmark:
go test -bench=. -benchmem
A flag -benchmem mostrará o número de alocações de memória por operação — um indicador chave para otimização.
2. Gerenciamento de memória: menos alocações, mais velocidade
O garbage collector (GC) do Go é eficiente, mas alocações frequentes tornam o processo mais lento. Otimizar a memória é uma das maneiras mais eficazes de acelerar a aplicação.
Pré-alocação de slices
Se você sabe o tamanho do slice antecipadamente, aloque-o usando make com capacity:
// Ruim: append realoca memória constantemente
var data []int
for i := 0; i < 100000; i++ {
data = append(data, i)
}
// Bom: pré-alocação
data := make([]int, 0, 100000)
for i := 0; i < 100000; i++ {
data = append(data, i)
}
A diferença de velocidade pode ser de 2 a 3 vezes, e o número de alocações cai para 1.
Usando sync.Pool para objetos temporários
Se você cria e destrói objetos com frequência (por exemplo, buffers), use um pool:
var bufPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
func process() {
buf := bufPool.Get().(*bytes.Buffer)
defer bufPool.Put(buf)
buf.Reset()
// trabalho com o buffer
}
Isso reduz a carga no GC e acelera a execução em loops.
Evite vazamentos de goroutines
Cada goroutine ocupa pelo menos 2-4 KB de stack. Se houver milhares delas, a memória cresce. Sempre use context.WithCancel para finalizar goroutines:
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go func() {
for {
select {
case <-ctx.Done():
return
default:
// trabalho
}
}
}()
3. Concorrência: uso eficiente de goroutines e canais
Go é famoso por suas goroutines leves, mas o uso incorreto pode prejudicar o desempenho.
Tamanho correto do pool de goroutines
Não inicie uma goroutine para cada tarefa sem limite. Use o padrão worker pool:
Blogs
Book Recommendations
Leia também
Criando sua própria linguagem de programação: DSL em JavaScript
Como se tornar um desenvolvedor TypeScript: dicas e currículo
Git workflow para TypeScript: como não enlouquecer com código tipado
Como passar em uma entrevista de Kotlin: perguntas avançadas
Formatação de números em moeda em JavaScript: 3 melhores maneiras