Pointers vs. Values em Go: como não matar a performance

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Pointers vs. Values em Go: A linha entre performance e segurança

\n\n

Imagine a situação: você está programando em Go, tudo corre bem, mas de repente percebe que a aplicação começa a "travar" com grandes volumes de dados. Você abre o profiler — e vê que os allocs/op (alocações por operação) estão nas alturas. Familiar? Provavelmente, o problema está em como você passa dados para as funções e os retorna. A pergunta «Pointers vs. values in parameters and return values» é uma das mais quentes no Stack Overflow (milhares de visualizações!), e hoje vamos analisá-la de forma viva e com exemplos reais.

\n\n

Go é uma linguagem que adora simplicidade. Mas essa simplicidade é enganosa: a escolha entre passar um valor (value) e um ponteiro (pointer) pode mudar drasticamente a velocidade do programa e o consumo de memória. Vamos entender quando usar cada um para que seu código voe, e não rasteje.

\n\n

Por que este tópico é tão importante?

\n\n

Ao contrário de Java ou Python, onde tudo é uma referência a objeto, em Go você escolhe explicitamente: passar uma cópia da estrutura ou seu endereço. Parece um detalhe. Mas, na verdade, é uma decisão fundamental que afeta:

\n\n
    \n
  • Velocidade de execução — copiar estruturas grandes leva tempo.
  • \n
  • Consumo de memória — cópias extras = alocações extras.
  • \n
  • Segurança dos dados — ponteiros permitem mutar o original, valores não.
  • \n
  • Trabalho com interfaces — interfaces em Go são um mecanismo engenhoso que funciona de forma diferente com ponteiros e valores.
  • \n
\n\n

Vamos direto ao ponto e ver o código.

\n\n

Exemplo nº1: Estrutura pequena

\n\n

Vamos começar com algo simples. Suponha que temos uma estrutura Point com duas coordenadas:

\n\n
type Point struct {\n    X, Y float64\n}\n\n// Passagem por valor\nfunc MoveByValue(p Point, dx, dy float64) Point {\n    p.X += dx\n    p.Y += dy\n    return p\n}\n\n// Passagem por ponteiro\nfunc MoveByPointer(p *Point, dx, dy float64) {\n    p.X += dx\n    p.Y += dy\n}
\n\n

No primeiro caso, criamos uma cópia da estrutura, a modificamos e retornamos uma nova. No segundo, alteramos o original diretamente. Qual opção é mais rápida? Para uma estrutura tão pequena (16 bytes) — quase não há diferença. Copiar 16 bytes é uma operação atômica, e o GC (coletor de lixo) não cria carga extra. Além disso, passar por valor pode ser até mais rápido, porque o ponteiro requer um dereference adicional.

\n\n

Conclusão: Para estruturas pequenas (até 4-8 campos de tipos básicos), use valores sem medo. É mais seguro e não gera overhead.

\n\n

Exemplo nº2: Estrutura grande

\n\n

Agora imagine que estamos trabalhando com dados de usuário:

\n\n
type User struct {\n    ID        int64\n    Name      string\n    Email     string\n    Avatar    []byte // 1 MB de imagem\n    CreatedAt time.Time\n    UpdatedAt time.Time\n}
\n\n

Aqui, Avatar é um slice, que em Go é um tipo referencial (cabeçalho: ponteiro para os dados, comprimento, capacidade). Mas a própria estrutura, ao ser copiada, copiará apenas o cabeçalho do slice (24 bytes), e não os bytes em si. No entanto, se a estrutura tiver muitos campos, copiar 100+ bytes a cada chamada de função já é perceptível.

\n\n

Vamos executar um benchmark simples:

\n\n
func BenchmarkPassByValue(b *testing.B) {\n    u := User{ID: 1, Name: "John", Avatar: make([]byte, 1024*1024)}\n    for i := 0; i < b.N; i++ {\n        _ = processUserByValue(u)\n    }\n}\n\nfunc BenchmarkPassByPointer(b *testing.B) {\n    u := &User{ID: 1, Name: "John", Avatar: make([]byte, 1024*1024)}\n    for i := 0; i < b.N; i++ {\n        _ = processUserByPointer(u)\n    }\n}
\n\n

Resultado (em hardware real):

\n
    \n
  • PassByValue: ~120 ns/op, 0 alocações
  • \n
  • PassBy

Blogs

Book Recommendations