Pré-alocação de Slice, strings.Builder e Pré-dimensionamento de Map
Muitos caminhos críticos em Go gastam mais tempo alocando do que computando.
Busque em todas as páginas da documentação
Muitos caminhos críticos em Go gastam mais tempo alocando do que computando.
Pré-dimensionar slices e mapas quando o comprimento é conhecido, construir strings com strings.Builder e provar ganhos com -benchmem em vez de adivinhar.
Cartão de receita de referência rápida - pronto para copiar e colar.
// Slice: dica de capacidade
out := make([]int, 0, len(in))
// Map: dica de tamanho
m := make(map[string]int, len(keys))
// String: Builder com Grow opcional
var b strings.Builder
b.Grow(estimatedLen)
b.WriteString("hello")
_ = b.String()go test -bench=. -benchmem ./...Quando usar isso:
allocs/op está alto em benchmarks ou perfis de heappackage alloc
import (
"strings"
"testing"
)
func JoinNaive(parts []string) string {
s := ""
for _, p := range parts {
s += p + ","
}
return s
}
func JoinBuilder(parts []string) string {
var b strings.Builder
b.Grow(len(parts) * 8)
for i, p := range parts {
if i > 0 {
b.WriteByte(',')
}
b.WriteString(p)
}
return b.String()
}
func FilterNaive(in []int) []int {
var out []int
for _, v := range in {
if v%2 == 0 {
out = append(out, v)
}
}
return out
}
func FilterPrealloc(in []int) []int {
out := make([]int, 0, len(in)/2)
for _, v := range in {
if v%2 == 0 {
out = append(out, v)
}
}
return out
}
func BenchmarkJoinNaive(b *testing.B) {
parts := []string{"alpha", "beta", "gamma", "delta"}
b.ReportAllocs()
for b.Loop() {
JoinNaive(parts)
}
}
func BenchmarkJoinBuilder(b *testing.B) {
parts := []string{"alpha", "beta", "gamma", "delta"}
b.ReportAllocs()
for b.Loop() {
JoinBuilder(parts)
}
}O que isso demonstra:
+= em loops aloca uma nova string a cada iteração.strings.Builder amortiza o crescimento com um único buffer de suporte.make([]T, 0, cap) evita cópias de crescimento repetidas de slice quando o tamanho é estimável.append de Slice dobra a capacidade quando cheia, copiando elementos a cada crescimento.make([]T, 0, n) aloca um array de suporte para até n elementos.strings.Builder mantém um buffer []byte; Grow estende previamente a capacidade antes das escritas.make(map[K]V, hint) reduz etapas de rehash quando a dica é precisa.| Estrutura | Fonte da dica | Risco se incorreto |
|---|---|---|
Saída []T | len(input) ou fração filtrada | Capacidade desperdiçada usa memória |
map[K]V | contagem de chaves distintas | Dica excessiva desperdiça buckets |
Builder.Grow | soma dos comprimentos das partes + separadores | Subdimensionar ainda cresce, apenas mais tarde |
// Reutilizar Builder - Reset limpa o buffer, mas mantém a capacidade
var builderPool sync.Pool
builderPool.New = func() any { return new(strings.Builder) }
func FormatID(id int) string {
b := builderPool.Get().(*strings.Builder)
b.Reset()
b.WriteString("id:")
b.WriteString(strconv.Itoa(id))
s := b.String()
builderPool.Put(b)
return s
}sync.Pool é opcional e somente após benchmem provar benefício - builders agrupados devem ser Reset antes da reutilização.strings.Join é idiomático e já otimizado.Builder.String() em um loop apertado sem reutilização - Ainda aloca a string final (imutável). Correção: Isso é esperado; elimine strings intermediárias, não o resultado.append([]T(nil), s...) - Aloca a cada vez. Correção: Pré-dimensionar o destino: dst := make([]T, len(s)); copy(dst, s).bytes.Buffer cegamente - bytes.Buffer é bom para binários; strings.Builder evita a cópia de []byte para string em String(). Correção: Escolha pelo tipo de saída.| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
strings.Join | []string conhecido de uma vez | Construindo incrementalmente com lógica entre partes |
bytes.Buffer | Protocolos binários | A saída final deve ser string sem cópia |
Buffer de pilha [N]byte fixo | IDs formatados minúsculos | Tamanho desconhecido ou grande |
sync.Pool para []byte | Buffers de codificador no caminho RPC | Uso raro - a sobrecarga do pool compensa |
Defina a capacidade quando souber ou puder limitar o comprimento final.
Para anexos raros, a clareza padrão do append é suficiente.
Não.
Um builder por goroutine, ou proteja com um mutex - agrupar por goroutine de solicitação é o típico.
Remove cópias de crescimento até o tamanho aumentado.
O String() final ainda aloca o resultado da string imutável.
Use chaves únicas esperadas, por exemplo, len(ids) quando as chaves são IDs.
Dicas superdimensionadas desperdiçam memória de bucket.
make([]T, n) define o comprimento n (valores zero incluídos).
Use make([]T, 0, n) quando você anexará n itens.
Compare -benchmem antes e depois com benchstat.
Anexe números a PRs de otimização.
Menos alocações diminuem a frequência do GC e o trabalho de marcação.
Combine com perfis de heap para confirmar a queda de churn.
Mapas não têm append.
Pré-dimensionar com make, então atribuir chaves no loop.
Escreva diretamente em http.ResponseWriter quando possível.
Evite construir strings gigantes apenas para copiá-las para o fio.
Testes, saída de CLI e logs únicos com strings pequenas.
Otimize apenas caminhos críticos medidos.
Versões da Stack: Esta página foi escrita para Go 1.26.x (padrão Green Tea GC, go fix modernizers - verificar patch na compilação), chi (última versão - verificar na compilação), gin (última versão - verificar na compilação), echo (última versão - verificar na compilação), google.golang.org/grpc (última versão - verificar na compilação), sigs.k8s.io/controller-runtime (última versão - verificar na compilação), kubebuilder (última versão - verificar na compilação), tinygo (última versão - verificar alvos de placa na compilação), wazero (última versão - verificar na compilação) e golangci-lint (última versão - verificar conjunto de linters na compilação).
Revisado por Chris St. John·Última atualização: 18 de jul. de 2026