logo hsb.horse
← Voltar para o índice de snippets

Snippets

Tamanho de Memória dos Tipos de Dados Go

Lista completa dos tamanhos de memória para cada tipo de dados Go e dicas práticas para codificação eficiente em memória.

Publicado: Atualizado:

Compreender a pegada de memória dos tipos de dados Go é crucial para escrever programas eficientes em memória. Isso se torna especialmente importante ao lidar com grandes conjuntos de dados, sistemas embarcados ou aplicações de alto desempenho onde a escolha das estruturas de dados impacta diretamente o desempenho.

Tamanho de Memória por Tipo de Dados

Tipo de DadosTamanho (Bytes)Notas
int / uint8 / 88 bytes em sistemas de 64 bits, 4 bytes em 32 bits
int8 / uint81 / 1
int16 / uint162 / 2
int32 / uint324 / 4
int64 / uint648 / 8
float324Ponto flutuante de precisão simples IEEE 754
float648Ponto flutuante de precisão dupla IEEE 754
complex648float32 × 2 (partes real e imaginária)
complex12816float64 × 2 (partes real e imaginária)
byte (alias para uint8)1
rune (alias para int32)4Ponto de código Unicode
bool1Usa 1 byte internamente
string16Ponteiro(8) + comprimento(8), dados armazenados separadamente
Slice (ex: []int)24Ponteiro(8) + comprimento(8) + capacidade(8)
Map (ex: map[string]int)8Ponteiro para estrutura interna
Channel (ex: chan int)8Ponteiro para estrutura interna
Struct (ex: struct{})00 quando não há campos
Interface16Info de tipo(8) + ponteiro para valor(8)

※ Os tamanhos são valores padrão para sistemas de 64 bits. Em sistemas de 32 bits, os ponteiros têm 4 bytes.

Exemplos de Implementação

Verificando Tamanhos

Você pode usar unsafe.Sizeof para verificar o tamanho dos tipos de dados em tempo de execução.

package main
import (
"fmt"
"unsafe"
)
func main() {
// Tipos básicos
var i int
var i8 int8
var f32 float32
var f64 float64
var b bool
var r rune
fmt.Printf("int: %d bytes\n", unsafe.Sizeof(i))
fmt.Printf("int8: %d bytes\n", unsafe.Sizeof(i8))
fmt.Printf("float32: %d bytes\n", unsafe.Sizeof(f32))
fmt.Printf("float64: %d bytes\n", unsafe.Sizeof(f64))
fmt.Printf("bool: %d bytes\n", unsafe.Sizeof(b))
fmt.Printf("rune: %d bytes\n", unsafe.Sizeof(r))
// String, slice, map
var s string
var slice []int
var m map[string]int
fmt.Printf("string: %d bytes\n", unsafe.Sizeof(s))
fmt.Printf("slice: %d bytes\n", unsafe.Sizeof(slice))
fmt.Printf("map: %d bytes\n", unsafe.Sizeof(m))
// Interface
var iface interface{}
fmt.Printf("interface{}: %d bytes\n", unsafe.Sizeof(iface))
}

Verificando Padding de Struct

A eficiência de memória das structs varia com base na ordenação dos campos. O padding pode causar consumo de memória inesperado.

package main
import (
"fmt"
"unsafe"
)
// Layout ineficiente
type BadLayout struct {
a bool // 1 byte + 7 bytes padding
b int64 // 8 bytes
c bool // 1 byte + 7 bytes padding
d int64 // 8 bytes
}
// Layout eficiente
type GoodLayout struct {
b int64 // 8 bytes
d int64 // 8 bytes
a bool // 1 byte
c bool // 1 byte + 6 bytes padding
}
func main() {
fmt.Printf("BadLayout: %d bytes\n", unsafe.Sizeof(BadLayout{})) // 32 bytes
fmt.Printf("GoodLayout: %d bytes\n", unsafe.Sizeof(GoodLayout{})) // 24 bytes
}

Capacidade de Slice e Alocação de Memória

Gerenciar adequadamente a capacidade de slices evita realocações desnecessárias.

package main
import (
"fmt"
)
func main() {
// Sem capacidade especificada, realocações podem ocorrer a cada append
s1 := []int{}
for i := 0; i < 1000; i++ {
s1 = append(s1, i)
}
// Pré-alocação de capacidade evita realocações
s2 := make([]int, 0, 1000)
for i := 0; i < 1000; i++ {
s2 = append(s2, i)
}
fmt.Printf("s1 len: %d, cap: %d\n", len(s1), cap(s1))
fmt.Printf("s2 len: %d, cap: %d\n", len(s2), cap(s2))
}

Pré-alocando Maps

Maps também se beneficiam da especificação de capacidade inicial, reduzindo custos de expansão dinâmica.

package main
import (
"fmt"
)
func main() {
// Sem capacidade especificada
m1 := make(map[int]string)
for i := 0; i < 1000; i++ {
m1[i] = fmt.Sprintf("value%d", i)
}
// Com capacidade pré-alocada
m2 := make(map[int]string, 1000)
for i := 0; i < 1000; i++ {
m2[i] = fmt.Sprintf("value%d", i)
}
fmt.Printf("m1 length: %d\n", len(m1))
fmt.Printf("m2 length: %d\n", len(m2))
}

Ideias de Aplicação e Técnicas de Otimização

1. Otimizar Ordenação de Campos de Struct

Coloque campos maiores primeiro e campos menores por último para minimizar o padding.

// Antes da otimização: 32 bytes
type Before struct {
flag1 bool // 1 + 7 padding
num1 int64 // 8
flag2 bool // 1 + 7 padding
num2 int64 // 8
}
// Após otimização: 24 bytes
type After struct {
num1 int64 // 8
num2 int64 // 8
flag1 bool // 1
flag2 bool // 1 + 6 padding
}

2. Definir Capacidade de Slice Apropriada

Quando a contagem final de elementos é previsível, especifique a capacidade com make para evitar realocações de memória.

// Ao adicionar 1000 elementos
items := make([]Item, 0, 1000)

3. Escolher Entre Arrays e Slices

Quando a contagem de elementos é fixa e não mudará, arrays são mais eficientes em memória que slices.

// Slice: 24 bytes (cabeçalho) + dados reais
s := []int{1, 2, 3}
// Array: 24 bytes (int × 3, apenas dados)
a := [3]int{1, 2, 3}

4. Minimizar Uso de Interface Vazia

interface{} consome 16 bytes para informações de tipo e ponteiro. Use genéricos ou tipos concretos quando o tipo é conhecido.

// Usando interface vazia (16 bytes)
var v interface{} = 42
// Usando tipo concreto (8 bytes)
var v int = 42

5. Usar Builder para Concatenação de Strings

Strings são imutáveis, então o operador + aloca nova memória a cada vez. Use strings.Builder para concatenação em massa.

import "strings"
var b strings.Builder
for i := 0; i < 1000; i++ {
b.WriteString("item")
}
result := b.String()

6. Reutilizar Objetos com sync.Pool

Objetos frequentemente criados e destruídos podem ser reutilizados com sync.Pool para reduzir a pressão do garbage collector.

import "sync"
var bufferPool = sync.Pool{
New: func() interface{} {
return make([]byte, 1024)
},
}
// Uso
buf := bufferPool.Get().([]byte)
defer bufferPool.Put(buf)

7. Usar Ponteiros para Evitar Copiar Structs Grandes

Ao passar structs grandes para funções, use ponteiros em vez de passagem por valor para reduzir custos de cópia.

// Passagem por valor: struct inteira é copiada
func ProcessValue(data BigStruct) {
// ...
}
// Passagem por ponteiro: apenas ponteiro (8 bytes) é copiado
func ProcessPointer(data *BigStruct) {
// ...
}

8. Identificar Áreas Problemáticas com Profiling de Memória

Use pprof para visualizar o uso de memória e identificar áreas para otimização.

import _ "net/http/pprof"
import "net/http"
func main() {
go func() {
http.ListenAndServe("localhost:6060", nil)
}()
// Lógica da aplicação
}

Acesse http://localhost:6060/debug/pprof/heap em um navegador ou analise perfis com go tool pprof.

Resumo

  • Tipos básicos têm tamanhos explícitos; strings, slices, maps e interfaces são baseados em ponteiros
  • A ordenação de campos de struct afeta o padding e a eficiência de memória
  • Especificar capacidade inicial para slices e maps reduz custos de realocação
  • Passe structs grandes por ponteiro e reutilize objetos frequentemente criados com sync.Pool
  • Use pprof para profiling de memória e identificar oportunidades de otimização

Ao aplicar esse conhecimento, você pode escrever programas Go eficientes em memória.