1 pontos por GN⁺ 2025-04-24 | 1 comentários | Compartilhar no WhatsApp
  • A linguagem Go tem quase nenhum comportamento indefinido e possui uma semântica de GC (coletor de lixo) simples
  • Em Go, é possível fazer gerenciamento manual de memória, e isso pode ser feito em cooperação com o GC
  • Arena é uma estrutura de dados para alocar memória de forma eficiente quando ela tem o mesmo tempo de vida, e o texto explica como implementar isso em Go
  • Explica como o GC gerencia a memória por meio do algoritmo Mark and Sweep
  • É possível melhorar o desempenho de alocação de memória usando Arena, e isso pode ser alcançado com várias otimizações
  • Busca melhorar o desempenho e minimizar a carga do GC por meio de remoção de write barrier, reutilização de memória, chunk pooling etc.
  • Apresenta padrões seguros e rápidos para processamento real de memória em grande escala com recursos como implementação de realloc, reutilização de Arena e inicialização (Reset)

Visão geral da alocação manual de memória baseada em Arena em Go

  • Go é uma linguagem segura graças ao comportamento claro do GC e à quase ausência de Undefined Behavior
  • Usando o pacote unsafe, é possível fazer controle direto da memória ajustado à implementação interna do GC
  • Este texto explica como criar em Go um alocador de memória baseado em Arena que coopera com o GC

Definição de Arena e por que ela é necessária

  • Arena é uma estrutura para alocar com eficiência objetos com o mesmo tempo de vida
  • Enquanto o append comum expande arrays de forma exponencial, a Arena adiciona novos blocos e fornece ponteiros
  • A interface padrão é a seguinte:
    • Alloc(size, align uintptr) unsafe.Pointer

Ponteiros e como o GC funciona

  • O GC funciona rastreando (mark) e recuperando (sweep) a memória
  • Para um GC preciso, ele usa metadados chamados pointer bits, que informam onde os ponteiros estão localizados
  • Se os ponteiros forem tratados incorretamente em uma Arena, o GC pode deixar de rastreá-los, o que pode causar erros de use-after-free

Como projetar uma Arena

  • A estrutura Arena tem campos como:
    • next, left, cap, chunks
  • Todas as alocações são tratadas com alinhamento de 8 bytes e, se faltar espaço, um novo chunk é criado com nextPow2
  • Em vez de alocar o chunk como []uintptr, ele é alocado com o tipo struct { A [N]uintptr; P *Arena }, permitindo que o GC rastreie a Arena

Como garantir a segurança de ponteiros na Arena

  • Quando são usados apenas ponteiros alocados dentro da Arena, o GC mantém a Arena inteira viva
  • Configura-se o ponteiro para referenciar a Arena, garantindo que toda a Arena sobreviva ao GC
  • O método de alocação da Arena faz o seguinte:
    • em allocChunk(), armazena o ponteiro da Arena no final do chunk

Resultados dos benchmarks de desempenho

  • Em comparação com new, a alocação com Arena mostrou em média ganho de desempenho de 2 a 4 vezes ou mais
  • Mesmo em situações com alta carga de GC, a abordagem com Arena mostrou desempenho mais de 2 vezes melhor
  • Otimizações como remoção de write barrier e uso de uintptr oferecem até 20% de melhora em alocações pequenas

Estratégias de reutilização de chunk e eliminação de heap

  • É possível reutilizar chunks com sync.Pool
  • Com runtime.SetFinalizer(), os chunks podem ser devolvidos ao pool quando a Arena desaparecer
  • O desempenho melhora bastante em alocações pequenas, mas em alocações grandes pode ficar mais lento que new

Inicialização e reutilização da Arena

  • O método Reset() pode devolver a Arena ao estado inicial
  • Embora seja arriscado, isso permite reutilizar a mesma estrutura sem realocar memória
  • Mesmo na reutilização, os chunks também são reaproveitados, aumentando bastante o desempenho

Implementação da funcionalidade Realloc

  • A Arena implementa a funcionalidade realloc, permitindo expansão dinâmica para a alocação mais recente
  • Quando isso não é possível, uma nova área de memória é alocada e o conteúdo é copiado

Conclusão e código completo fornecido

  • Com um entendimento profundo do mecanismo de GC do Go e com base em sua implementação interna, o texto conclui um gerenciador de memória baseado em Arena
  • É uma estrutura que oferece segurança e desempenho ao mesmo tempo e, se usada corretamente, é muito útil para lidar com estruturas de dados em larga escala
  • O código completo inclui a struct Arena e elementos como New, Alloc, Reset, allocChunk, finalize etc.

1 comentários

 
GN⁺ 2025-04-24
Comentários no Hacker News
  • Este artigo é uma leitura divertida

    • Se você gostou deste artigo ou quer controlar melhor a alocação de memória em Go, vale a pena conferir o pacote que escrevi
    • Eu gostaria de receber feedback ou ver outras pessoas usando
    • Este pacote aloca a própria memória separadamente do runtime, contornando completamente o GC
    • Ele não permite tipos ponteiro na alocação, mas substitui isso por um tipo Reference[T] que oferece a mesma funcionalidade
    • A liberação de memória é manual, então não dá para depender do garbage collector
    • Em Go, esses alocadores customizados normalmente seguem a ideia de arenas que suportam grupos de alocação criados e destruídos juntos
    • Porém, o pacote offheap tem como objetivo construir grandes estruturas de dados de longa duração para zerar o custo de garbage collection
    • Coisas como grandes caches em memória ou bancos de dados
  • Recentemente, ao fazer tuning de performance em Go, acabei usando um design de arena muito parecido para maximizar o desempenho

    • Em vez de ponteiros unsafe, usei slices de bytes como buf e chunks
    • Fiz isso, mas não ficou mais rápido e ficou muito mais complexo
    • Preciso conferir de novo antes de ter 100% de certeza
  • Alguns pontos de melhoria fáceis

    • Se você começa com slices pequenas e alguns payloads acabam sendo adicionados em grande volume, escreva seu próprio append para aumentar o cap de forma mais agressiva antes de chamar o append embutido
    • unsafe.String é útil para passar strings a partir de slices de bytes sem alocação
    • Você precisa ler os avisos com atenção e entender o que está fazendo
  • Não é sobre o tema, mas gostei do minimapa na lateral

    • Ele é útil em artigos técnicos longos quando você está navegando pelo conteúdo ou voltando para consultar algo que já leu
    • Fico pensando como poderia adicionar isso ao meu site
    • Muito legal mesmo
  • Resumo para quem não quer ler o artigo longo

    • O OP construiu um alocador de arena em Go usando unsafe para acelerar o trabalho de alocação
    • Isso é especialmente útil ao alocar muitas coisas que são criadas e destruídas juntas
    • O principal problema é que o GC do Go precisa conhecer o layout dos dados, especialmente onde ficam os ponteiros, para funcionar corretamente
    • Se você aloca bytes brutos com unsafe.Pointer, o GC não consegue enxergar direito o que está sendo apontado dentro da arena e pode liberar isso por engano
    • Porém, para fazer isso funcionar enquanto os ponteiros apontarem para outras coisas dentro da mesma arena, ele mantém a arena inteira viva se alguma parte dela ainda estiver referenciada
    • (1) mantendo um slice (chunks) que aponta para todos os grandes blocos de memória que a arena obteve do sistema e
    • (2) usando reflect.StructOf para criar um novo tipo que inclua campos de ponteiro adicionais neste bloco
    • Assim, quando o GC encontra um ponteiro para os chunks, também encontra um back pointer e marca a arena como viva, preservando o slice de chunks
    • Depois, o artigo apresenta técnicas interessantes de otimização para remover várias verificações internas e write barriers
  • Relacionado: discussão sobre adicionar "regiões de memória" à biblioteca padrão

    • Proposta anterior de arena
  • Conteúdo interessante

    • Fico curioso sobre como as pessoas que constroem alocadores off-heap ou no estilo arena em Go normalmente testam ou fazem benchmark da segurança de memória e da interação com o GC
  • Go prioriza não quebrar o ecossistema

    • Isso permite assumir que a Lei de Hyrum protegerá certos comportamentos observáveis específicos do runtime
    • Se essa afirmação estiver correta, Go é uma linguagem sem saída evolutiva
    • Nesse caso, não tenho certeza de que Go seja interessante
  • Uma nota meta simples

    • Este artigo é realmente muito longo, então não tive tempo de ler os detalhes do contexto
    • Por exemplo, a seção "Mark and Sweep" ocupa mais de 4 páginas na tela do meu notebook
    • Essa seção começa só depois de mais de 5 páginas desde o início do artigo
    • Fico pensando se isso é resultado de IA ajudando a escrever as seções e deixando tudo abrangente demais
    • Gerar conteúdo é fácil, mas não foram tomadas decisões de edição para preservar o que importa
    • Eu só queria saber sobre a parte do alocador de arena e não preciso de um tutorial sobre garbage collection