9 pontos por GN⁺ 2025-06-14 | 2 comentários | Compartilhar no WhatsApp
  • Uma retrospectiva escrita pelo próprio Jason Evans, que liderou o desenvolvimento do jemalloc, revisitando cerca de 20 anos da trajetória do jemalloc em 5 fases e registrando com franqueza sucessos e fracassos, os limites de projetos open source e o processo de declínio causado por mudanças internas nas empresas
  • O alocador de memória jemalloc começou em 2004 e foi amplamente usado por cerca de 20 anos, mas recentemente o desenvolvimento oficial foi interrompido devido a mudanças internas na Meta
  • Acumulou experiência em otimização de desempenho e portabilidade em várias etapas, incluindo a integração inicial ao FreeBSD, a resolução de problemas de desempenho do Firefox e a adoção em larga escala pelo Facebook
  • Ao enfrentar diversos desafios, como problemas de fragmentação de armazenamento e de escalabilidade, foram adicionados vários recursos, incluindo melhorias de desempenho, coleta de estatísticas e infraestrutura de testes
  • Com a mudança na cultura corporativa dentro da Meta, o declínio no investimento técnico e a ausência de pessoal-chave de manutenção levaram à interrupção do desenvolvimento de longo prazo
  • A remoção do Valgrind e a falta de feedback de usuários externos, entre outros fatores, funcionaram como limitações estruturais na desconexão com o ecossistema open source
  • Ainda existe a possibilidade de novos avanços por meio de forks, mas o desenvolvimento principal oficial não deve mais continuar

Visão geral do jemalloc

  • O jemalloc é um alocador de memória open source concebido pela primeira vez em 2004, desenvolvido com o objetivo de melhorar desempenho e escalabilidade, e usado ativamente por 20 anos em grandes projetos open source e infraestruturas de big tech
  • Como o desenvolvimento principal foi recentemente interrompido, espera-se daqui em diante a manutenção por meio de forks ou por organizações individuais

Fase 0: Lyken

  • Em 2004, durante o desenvolvimento da linguagem de programação para computação científica chamada Lyken, tudo começou com um alocador de memória manual
  • O alocador interno do Lyken ficou funcionalmente completo em maio de 2005
  • Depois, ao ser integrado ao FreeBSD, o alocador foi removido do Lyken, restando apenas um wrapper fino sobre o alocador do sistema
  • O motivo da remoção foi que, após a integração ao FreeBSD, ficou claro que só faltavam ao alocador do sistema alguns pontos específicos, como o rastreamento de alocações por thread
  • Curiosamente, mais tarde o jemalloc ganhou a funcionalidade de coleta de estatísticas que já era necessária na época do Lyken

Fase 1: FreeBSD

  • Em 2005, quando os computadores multiprocessados estavam se tornando comuns, o FreeBSD usava o phkmalloc, mas ele não era adequado para ambientes com threads paralelas
  • Como o alocador do Lyken tinha vantagens claras em escalabilidade, ele foi integrado ao FreeBSD e logo recebeu o nome jemalloc
  • No entanto, surgiram graves problemas de fragmentação em aplicações como o KDE, a ponto de sua viabilidade ser colocada em dúvida
  • A causa da fragmentação era o método de alocação em um espaço unificado sem separação por tamanho e, após pesquisa, a estrutura foi radicalmente alterada para um algoritmo de separação de regiões por tamanho
  • O conteúdo desse processo foi registrado no artigo da BSDCan de 2006

Fase 1.5: Firefox

  • Em 2007, antes do lançamento do Mozilla Firefox 3, a fragmentação de memória era um grande problema no ambiente Windows
  • Portar o jemalloc para Linux foi fácil, mas para Windows foi complicado
  • O jemalloc presente na libc do FreeBSD foi forkado pela Mozilla, que trabalhou na melhoria de portabilidade e compatibilidade
  • Com o tempo, a Mozilla contribuiu com muitas melhorias para o upstream do jemalloc, mas a versão em fork sempre apresentou desempenho superior
  • Não está claro se isso se deveu a regressões de desempenho ou a otimizações ajustadas para ambientes específicos

Fase 2: Facebook

  • Quando entrou no Facebook em 2009, o maior fator limitante do jemalloc era a falta de ferramentas, como profiling e detecção de vazamentos
  • Para resolver isso, foi introduzido no jemalloc 1.0.0 um recurso de heap profiling compatível com pprof
  • Depois da migração do desenvolvimento para o GitHub, muitos aprimoramentos foram feitos junto com usuários e contribuidores externos, incluindo infraestrutura de testes, suporte a Valgrind, estatísticas em JSON e um novo gerenciamento de páginas
  • Internamente, o enorme sistema de telemetria do Facebook ajudou muito na otimização de desempenho e na prevenção de regressões
  • 3.x: introdução da infraestrutura de testes e do suporte a Valgrind
  • 4.x: adição de decay-based purging e estatísticas em JSON
  • 5.x: mudança de uma arquitetura baseada em chunk para uma baseada em extent, preparando o uso de huge pages de 2MiB
  • A análise de desempenho baseada em telemetria dentro do Facebook teve papel decisivo nas otimizações
  • A remoção do suporte a Valgrind na versão 5.0.0 foi decidida internamente por falta de uso, mas houve forte reação externa, inclusive de desenvolvedores Rust
  • Depois, com as mudanças organizacionais no Facebook/Meta, a equipe do jemalloc foi reduzida e a estratégia de negócios passou a priorizar eficiência em vez de investimentos técnicos centrais
  • Com isso, o desenvolvimento de grandes recursos, como Huge Page Allocation, ficou estagnado, e alguns trabalhos não foram concluídos
  • Após a saída de Evans em 2017, Qi Wang manteve o desenvolvimento por vários anos
  • Mesmo após a transição da liderança da equipe, vários contribuidores continuaram mantendo o projeto, mas acabou faltando alguém responsável por uma visão de longo prazo

Fase 4: Estagnação

  • Atualmente, o desenvolvimento upstream do jemalloc foi encerrado, e a Meta também passou a seguir uma direção separada conforme suas necessidades internas
  • A dívida técnica da base de código existente é grande, então um refactoring amplo precisa vir antes de qualquer retomada
  • As exigências do Facebook/Meta e as dos usuários externos não coincidem mais
  • Se o desenvolvimento for retomado no futuro, serão necessárias centenas de horas para resolver a dívida técnica primeiro, e o autor não tem motivação para isso
  • Com base no branch dev ou na versão 5.3.0, forks externos são possíveis, então ainda pode surgir a qualquer momento um novo projeto baseado em fork

Retrospectiva e lições

  • O conflito causado pela remoção do suporte a Valgrind teve origem na falta de entendimento sobre os usos externos
  • O fato de o jemalloc estar sendo usado no Android também só foi descoberto dois anos depois
  • O projeto era totalmente aberto no GitHub, mas não houve continuidade de contribuidores centrais em organizações externas
    • Nem Mike Hommey, do Firefox, nem a tentativa de migração para CMake chegaram a ser concluídos
  • Pela experiência relatada, simplesmente abrir um projeto não o transforma em um projeto independente sustentável
  • O texto enfatiza que open source não se sustenta apenas por ser público; é essencial formar contribuidores centrais e garantir governança

Considerações finais

  • O jemalloc foi uma experiência especial até mesmo para o autor, que por mais de 25 anos foi um defensor do garbage collection
  • Embora esteja novamente focado no desenvolvimento de sistemas com garbage collection, ele expressa profunda gratidão a todos que colaboraram com o jemalloc

2 comentários

 
ganadist 2025-06-14

Também existe uma tradução do texto completo.
https://rosettalens.com/s/ko/jemalloc-postmortem

 
GN⁺ 2025-06-14
Comentários no Hacker News
  • Entendo a decisão de arquivar o repositório upstream. Antes de sair da Meta, nossa equipe do jemalloc já não tinha capacidade para responder a todo tipo de issue que aparecia no GitHub (por exemplo, uma vez alguém abriu uma issue porque os testes não passavam em Itanium, e isso me pareceu meio cômico). Ainda assim, é triste ver a situação chegar a esse ponto. Ainda acho que o jemalloc continua sendo, entre as implementações de malloc de uso geral, a opção com melhor desempenho e mais fácil de usar. O TCMalloc também é excelente, mas considero realmente difícil de usar se você não utiliza bazel (hoje em dia ficou um pouco mais fácil exportar como biblioteca estática com a adição de cc_static_library no bazel 7.4.0, mas isso ainda continua sendo uma limitação importante). Estou pensando em perguntar ao Qi se seria possível fazer um último release 6.0 antes de arquivar o repositório de novo. Se for um release final, talvez valha a pena modernizar um pouco as configurações padrão. Por exemplo, uma grande melhoria seria desativar por padrão a opção cache oblivious, cujo nome acaba sendo confuso, para evitar que a classe de tamanho de 16 KiB infle inutilmente para 20 KiB. Não estou tentando culpar a escolha anterior (ou seja, a decisão inicial do Jason); quando discutimos isso com o Qi e o David na época, havia uma justificativa plausível, porque em geral a associatividade de TLB era muito menor do que hoje. Em linha parecida, também seria uma boa mudança aumentar o page size padrão de 4 KiB para algo maior, como 16 KiB. Isso elevaria o limite das grandes size-classes (o ponto em que várias alocações deixam de ir para slabs e passam a receber regiões individuais) de 16 KiB para 64 KiB, o que teria bastante impacto. Antes de sair da Meta, cheguei a avaliar aplicar essa mudança em um serviço interno importante; foi uma otimização que reduzia o uso de CPU em alguns pontos percentuais, em troca de um pequeno aumento de memória por fragmentação de RAM. Há outras coisas que eu também gostaria de mudar (por exemplo, alterar o padrão de metadata_thp de disabled para auto, e mudar o dimensionamento de extents dos slabs para reduzir fragmentação mesmo aceitando cerca de 1% de desperdício em vez de exigir múltiplos exatos do tamanho de página). Ainda assim, as configurações mencionadas acima seriam as mudanças mais relevantes

    • Fui eu mesmo quem abriu a issue sobre a falha da suíte de testes em Itanium

    • É por esse tipo de relato real e insight de quem esteve por dentro que eu continuo voltando ao Hacker News. Fiquei curioso para saber por que é tão difícil usar o TCMalloc sem bazel (pergunto por curiosidade genuína)

    • Eu gostaria que esse tipo de conhecimento interno importante fosse publicado em materiais mais amplos, como documentação oficial ou posts de blog. No momento, a documentação oficial parece pobre demais. Seria ótimo se o know-how acumulado em tantos anos de trabalho dentro da Meta fosse compartilhado antes de se perder com o tempo

    • É uma pena ver um software tão excelente acabar subaproveitado por causa da complexidade do build e da integração

    • Você mencionou que “a equipe do jemalloc não tinha capacidade suficiente para lidar com issues aleatórias no GitHub”, e fiquei com uma dúvida. Pelo que eu entendia, havia gente suficiente na Meta cuidando desse projeto, então queria saber qual era o motivo de a gestão das issues não fluir bem. Se eu entendi errado a situação, por favor me corrija

  • Quero dizer o quanto o trabalho do Jason impacta o nosso dia a dia. Nossa empresa é relativamente grande e processa centenas de milhões de imagens e vídeos por dia. Nos primeiros anos, sofremos demais com problemas de fragmentação de memória. Aí um dia adotamos jemalloc, mudamos literalmente só duas linhas no Dockerfile, e todos os problemas desapareceram. Hoje somos uma empresa que fatura dezenas de bilhões, e usamos jemalloc em todos os serviços e Dockerfiles. Minha gratidão é profunda

    • Na prática, muitos serviços de processamento de imagem em golang recomendam ou usam jemalloc. Entre os 3 principais no tópico resize-images (em 2025-06-13), o imaginary usa desta forma no Dockerfile, o imgproxy também é discutido no repositório do imaginary com base em documentação arquivada, e o imagor também usa jemalloc neste ponto aqui

    • Pergunto por curiosidade sincera (sem ironia nenhuma): vocês também fizeram alguma doação? Tenho a impressão de que expressar gratidão com dinheiro é a melhor forma de agradecer

  • Sobre a opinião de que “o jemalloc foi removido dos binários do Rust mais cedo do que deveria”, queria compartilhar que, na prática, aquela issue foi apenas uma entre várias causas. Dá para ver o contexto neste comentário. E o jemalloc só foi removido de vez dois anos depois de a issue ter sido levantada pela primeira vez (referência: este PR)

    • É curioso que, entre os vários problemas mencionados ali, a questão do tamanho de página hardcoded em arm64 continue sem solução no upstream até hoje. Por causa disso, desenvolvedores de apps acabam tendo de distribuir múltiplos binários Linux separados para arm64 ou abandonar suporte a algumas plataformas. Fico me perguntando se a adoção de page size dinâmico (com aplicação dinâmica de patch binário no estilo ftrace para desempenho) teria deixado tudo significativamente mais lento do que hoje
  • Acabei criando o hábito de sempre usar jemalloc em todos os game engines que fiz ao longo dos anos. No ambiente win32, ele é muito mais rápido que o alocador padrão, e também tem a vantagem de usar o mesmo alocador em todas as plataformas. Conheci o jemalloc quando ele foi integrado ao FreeBSD, e desde então não usei outra coisa. Tenho orgulho de pensar que muitos jogadores se divertiram graças ao jemalloc

    • O allocator padrão do Windows é realmente muito ruim. Jemalloc é o melhor
  • Achei o texto muito bom — fiquei curioso para saber se o Facebook (agora Meta) realmente não usa mais o jemalloc em si, ou se só entrou em modo de manutenção. Também me pergunto se existe a possibilidade de terem migrado para tcmalloc ou outro allocator. Havia a frase de que “na engenharia de infraestrutura do Facebook, o foco se deslocou de investimento em tecnologias centrais para ROI”

    • Saí da Meta há quase dois anos (embora eu ache que isso não tenha mudado muito), mas o jemalloc ainda continua sendo usado com link estático em todos os binários da Meta. Sobre a pergunta de se seria fácil migrar para tcmalloc ou outro allocator, a resposta é que o jemalloc está profundamente acoplado ao ecossistema interno da empresa, então é bem mais difícil do que parece. Vai desde o plumbing de telemetria do Strobelight até várias extensões usadas especificamente com jemalloc (por exemplo, arenas manuais usando custom extent hooks diretamente), além do fato de que a maioria dos aplicativos evoluiu justamente para extrair o máximo possível das características do jemalloc

    • A mudança recente mais importante é que todos os mantenedores de longo prazo do jemalloc saíram. Mas, curiosamente, o interesse por esse projeto dentro do Facebook aumentou mais do que antes, e depois de algumas controvérsias recentes, espero que agora ele siga numa direção que considere o Qi, o Jason e também os usuários externos

    • A Meta ainda desenvolve ativamente sua própria versão em fork aqui

  • Foi uma honra participar de algo que teve impacto por tanto tempo, do Firefox ao Facebook

    • Também queria aproveitar o momento para deixar meus agradecimentos aqui. Eu não esperava que esse texto fosse sair hoje, mas foi uma honra ter participado, nem que fosse de uma pequena parte dessa longa jornada. Também quero agradecer ao @je, ao qi, ao david e a todos os contribuidores da época e posteriores

    • Acho que sua liderança, conduzindo o Facebook a continuar investindo em tecnologias centrais, deu o maior retorno possível. Inovações como GraphQL, PyTorch e React só puderam existir porque essa base estava lá

  • A citação do FTA — “Quando as opções são impossíveis, as pessoas acabam 1) tomando decisões ruins sob extrema pressão, 2) se conformando sob extrema pressão, ou 3) procurando desvios” — parece um retrato difícil de imaginar como clima de trabalho

    • Minha experiência amarga é que quase todos os lugares em que trabalhei desde 2008 eram mais ou menos assim
  • Acho que o jemalloc é o único allocator no macOS que consegue sobrescrever malloc/free de forma tão transparente quanto um LD_PRELOAD (pelo menos por volta de 2020). Ele consegue entrar como alocador padrão com facilidade via o modelo de zones e também atende bem às exigências específicas da Apple para allocators. Outros allocators third-party frequentemente fracassavam por causa dessas exigências

    • Só que isso só funciona sob a hipótese de que o allocator de sistema do macOS não mude suas estruturas internas, e lembro que a Apple já quebrou isso em pelo menos duas ocasiões ao alterar a implementação

    • Pelo que eu sei, o mimalloc também consegue funcionar da mesma forma, mas não tenho certeza

  • Pelo que vejo, o jemalloc melhorou em todos os aspectos em relação ao malloc da glibc e, olhando benchmarks, quase sempre entrega desempenho superior; então, como alguém de fora, sempre tive curiosidade de por que ele não é o allocator padrão

    • No FreeBSD, ele já é o padrão. Se você quiser trocar o malloc, provavelmente é mais fácil trocar a libc junto pela libc do FreeBSD, e aí naturalmente também faz sentido trocar o kernel para FreeBSD. Quando nos fundimos com o Facebook, eu ficava animado apresentando o jemalloc para o pessoal de lá, mas para quem já usava FreeBSD isso era algo tão natural que nem chamava atenção

    • Não sou engenheiro de allocators, então não é uma opinião especializada, mas certa vez conversei com um engenheiro que cuidava do allocator do sistema operacional. Segundo ele, allocators customizados tendem a dar uma vantagem enorme a um processo individual em alocação de memória, mas pioram a justiça da alocação no sistema como um todo. Do ponto de vista do allocator do sistema, quando cada processo segue padrões diferentes, otimizar o conjunto inteiro fica mais difícil. Por isso, em ambientes de serviço, o jemalloc é tão recomendado quando a premissa é “o que importa é o meu processo agora”

    • Não acho que exista uma razão técnica para o jemalloc não ser o allocator padrão. Na prática, ele já é o padrão no FreeBSD (como o artigo menciona). Pelo que entendo, essa questão é mais política do que técnica

    • O jemalloc já foi validado em produção em larga escala, tem uma licença extremamente permissiva e desempenho comprovado. Então, fora “pureza ideológica” e inércia legada, eu realmente me pergunto quem ganha com a insistência no malloc da glibc. Dá vontade de questionar por que ainda continuam se escondendo atrás da desculpa da “compatibilidade”

    • Antigamente, um grande problema era que allocators alternativos simplesmente nunca devolviam a memória liberada ao sistema operacional e só ficavam segurando páginas sujas (isso acabou melhorando depois, mas é um exemplo clássico de prioridades diferentes entre allocators). E, na prática, a maioria dos processos só roda uma thread principal ou tem threads quase sempre ociosas. Allocators otimizados para multithreading podem ser exagerados e até custosos nesses ambientes. Também vale mencionar que muita gente ignora o fato de que, na prática, o custo de o kernel zerar páginas e o custo de um processo em user space zerá-las para reutilização interna não são tão diferentes assim

  • Eu preferiria que o link para o post do blog fosse adicionado ao repositório no GitHub. Acho importante que quem visitar o repositório no futuro tenha esse contexto para consultar