3 pontos por GN⁺ 2026-01-03 | 2 comentários | Compartilhar no WhatsApp
  • Analisa os limites de desempenho do Bundler e compara por que o gerenciador de pacotes Python uv é rápido
  • A velocidade do uv não vem da linguagem Rust em si, mas de um projeto estrutural com downloads em paralelo, cache global e tratamento de dependências baseado em metadados
  • O Bundler acopla os processos de download e instalação, o que impõe restrições ao paralelismo; separar essas etapas pode trazer grandes melhorias
  • É possível reduzir a duplicação entre RubyGems e Bundler com integração de cache global, instalação via hardlink e integração do resolvedor PubGrub
  • Mesmo sem reescrever a linguagem, a maior parte dos ganhos de desempenho pode ser alcançada dentro do código Ruby, aproximando-se da velocidade do uv

Comparação de desempenho entre Bundler e uv

  • A partir da pergunta levantada na RailsWorld, “por que o Bundler não é tão rápido quanto o uv?”, foi investigado o gargalo de desempenho do Bundler
  • O autor afirma estar convencido de que o Bundler pode atingir um nível de velocidade comparável ao do uv, e destaca que a diferença de desempenho é uma questão de projeto, não de linguagem
  • Citando o texto de Andrew Nesbitt, “How uv got so fast”, analisa se as principais otimizações do uv poderiam ser aplicadas ao Bundler

Reescrever em Rust ou não

  • É verdade que o uv foi escrito em Rust, mas a causa essencial da velocidade não é o Rust em si
  • Se a remoção dos gargalos do Bundler levar ao ponto em que “reescrever em Rust seja a única melhoria restante”, isso já seria considerado um sucesso
  • Reescrever em Rust oferece liberdade para tentar projetos experimentais sem as restrições de compatibilidade existentes, mas não é um requisito

Gargalos estruturais do Bundler

  • O Bundler combina o download do gem e a instalação em um único método, o que impede downloads paralelos
    • No código de exemplo, o método install executa fetch_gem_if_not_cached e install em sequência
    • Por isso, gems com relação de dependência (a -> b -> c) só podem ser instalados de forma sequencial
  • Em experimentos, quando há dependências, o processo leva mais de 9 segundos; já gems independentes (d, e, f) são concluídos em menos de 4 segundos com downloads paralelos
  • Se download e instalação forem separados, é possível manter as regras de dependência e ainda permitir paralelismo
    • Propõe-se separar em quatro etapas (download → descompactação → compilação → instalação)
    • Para gems Ruby puros, flexibilizar a ordem de instalação das dependências pode trazer ganhos extras de velocidade

Otimizações de cache e instalação

  • O método de cache global e instalação por hardlink do uv também pode ser aplicado ao Bundler
    • Atualmente, Bundler e RubyGems usam caches separados por versão do Ruby
    • É necessário unificar isso em um cache compartilhado com base em $XDG_CACHE_HOME
    • A instalação por hardlink pode ser aplicada depois que o cache estiver unificado
  • O Bundler já usa o resolvedor de dependências PubGrub, mas o RubyGems ainda usa molinillo
    • A integração dos resolvedores dos dois sistemas é o ponto-chave para eliminar dívida técnica

Aplicabilidade de elementos de otimização ligados ao Rust

  • A desserialização zero-copy pode ter alguma aplicabilidade na etapa de parsing YAML do RubyGems
  • O GVL (Global VM Lock) do Ruby não impõe grandes restrições ao paralelismo em tarefas centradas em IO
    • Operações de IO e ZLIB liberam o GVL, permitindo execução paralela
    • Porém, ao gravar arquivos pequenos, o overhead de gerenciamento do GVL pode prejudicar o desempenho
    • Já há trabalhos em andamento dentro do Ruby para melhorar isso
  • Otimização de comparação de versões: o uv acelera comparações ao codificar versões como inteiros u64
    • No Ruby, também seria possível converter Gem::Version para uma base inteira e melhorar o desempenho do resolvedor
    • Já houve tentativas de refatoração relacionadas, mas elas foram adiadas por problemas de compatibilidade retroativa

Conclusão e próximos passos

  • A velocidade do uv vem mais de um projeto que elimina trabalho desnecessário do que da linguagem, e o Bundler também pode evoluir na mesma direção
  • RubyGems e Bundler já têm uma estrutura moderna de gerenciamento de pacotes, o que torna realista atingir um nível de velocidade comparável ao do uv
  • O maior desafio é manter compatibilidade com código legado
  • Mesmo sem reescrever em Rust, 99% da melhoria de desempenho é possível dentro do código Ruby, e o 1% restante seria mínimo
  • Em um texto futuro, o autor pretende abordar o profiling real do Bundler e do RubyGems, além das causas concretas dos gargalos

2 comentários

 
iolothebard 2026-01-06

Falar é fácil. Quero ver o código!

 
GN⁺ 2026-01-03
Comentários do Hacker News
  • Não conheço tão bem a estrutura do Bundler, mas acho que a maior melhoria seria adotar o design de cache do uv
    Um dos principais motivos de o uv ser rápido está na estrutura do cache, e isso pode ser reproduzido em outras linguagens e ecossistemas
    No entanto, a parte de ignorar o limite superior de requires-python não é por desempenho, e sim para uma resolução de dependências melhor
    Por exemplo, se um projeto exige Python 3.8 ou superior, mas alguma dependência impõe a restrição <4, a instalação fica impossível no Python 4
    O uv resolve para todas as versões suportadas, então ignorar o limite superior quase não economiza tempo
    A discussão relacionada pode ser vista no fórum Python Discuss

  • Depois do PEP 658, a Simple Repository API do Python passou a fornecer metadados diretamente, e o RubyGems.org já oferece informações parecidas
    Mas só dá para saber se há native extension depois de descompactar o gem
    Então foi sugerido que, se essa informação fosse adicionada diretamente aos metadados do RubyGems.org, talvez fosse possível paralelizar completamente a árvore de instalação de dependências

    • Eu pensei a mesma coisa, mas existe a possibilidade de os dados do gemspec e os metadados do RubyGems.org divergirem
      Lembro que, quando trabalhei no RubyGems.org, os metadados eram extraídos por versão
      Seria preciso reprocessar os gemspecs de versões antigas, e isso pode ser uma mudança arriscada de metadados
      Então talvez seja difícil aplicar isso às versões antigas, mas no futuro parece possível melhorar para descobrir a ordem de instalação sem unpack
  • Gosto que o Aaron esteja focando em melhorias algorítmicas reais, em vez de reescrever o Bundler em Rust

    • Ganho de velocidade é bom, mas eu preciso mais de uma funcionalidade que também gerencie a própria instalação do Ruby
      Esse ambiente caótico com várias ferramentas de gerenciamento de versão e versões do Ruby misturadas é realmente frustrante
    • Como o Aaron é da Shopify, fico com sentimentos confusos por não haver menção ao projeto gem.coop
      Acho que o problema não é só velocidade, mas também controle e a direção do ecossistema
      O Ruby passou os últimos 10 anos focando em velocidade, mas a qualidade da documentação e a gestão da comunidade eram ainda mais importantes
      Já passou da hora de pensar seriamente sobre por que a linguagem está em declínio e de insistir em ideias diversas
  • Há também o texto relacionado How uv got so fast (dezembro de 2025, 457 comentários)

  • Para deixar o RubyGems mais rápido, o essencial é registrar/colocar em banco de dados a lista de arquivos de cada gem
    Assim, não seria necessário vasculhar o sistema de arquivos a cada require
    Se alguém modificar um gem diretamente, os metadados precisariam ser hasheados de novo, mas edições manuais já não são recomendadas de qualquer forma

    • Já escrevi um código parecido com isso antes, e mesmo sem cache em disco houve um grande ganho de velocidade ao gerar hashes na hora
      Hoje deve estar ultrapassado, mas ainda tenho carinho por esse mini projeto
      Código: fastup
    • Otimizar o “bundle install” é atacar o problema pelo lado errado
      O problema real é a estrutura em que o $LOAD_PATH adiciona todos os gems e causa uma explosão combinatória
      O fato de existirem vários projetos de cache prova que esse é um problema real
      Já tive app levando vários minutos para iniciar, e consegui reduzir isso em minutos manipulando o load path
    • Tentei lidar com isso em runtime, mas foi difícil implementar porque o Ruby não tem estruturas de dados eficientes o bastante
    • Na verdade, isso já é o que o bootsnap faz
      Eu já sugeri integrar o bootsnap ao bundler, mas a ideia foi recusada
  • Foi interessante a explicação da estrutura do RubyGems
    Um gem é um arquivo tar, e o YAML GemSpec dentro dele declara as dependências
    O RubyGems.org fornece essas informações via API, então dá para verificar dependências sem eval
    Só que YAML é um formato pouco eficiente para parsing, então alternativas como JSON ou protobuf talvez fossem melhores
    Ainda assim, se o gemserver já retorna as informações de dependência, provavelmente não é um grande problema

    • YAML não é grande coisa, mas no tamanho típico de um gemspec o impacto de desempenho deve ser mínimo
    • Se for um lockfile usado só para revisão, não para edição manual, daria para criar um parser simples removendo os recursos complexos do YAML
      Ex.: uma estrutura contendo apenas versão, dependências e hash
    • Na prática, esse tipo de metadado já é parseado e armazenado previamente no banco de dados por RubyGems ou PyPI
      Esse também é um dos motivos de o uv ser rápido — ele consegue calcular dependências sem baixar os pacotes
  • Já fiz um vídeo de protótipo melhorando a forma como gems são instalados
    how_gems_should_be.mov

  • Os fibers do Ruby (ou a biblioteca Async) costumam ser superestimados
    Assim como com threads, problemas de coordenação em nível mais alto, como pool de conexões, continuam existindo
    Mesmo assim, processar de forma assíncrona tarefas de instalação limitadas por IO pode trazer ganhos de desempenho relevantes

    • Para extrair mais desempenho em Ruby puro,
      1. usar um formato de índice rápido de parsear (gist relacionada)
      2. tratar o download inicial com threads
      3. separar descompressão e post-install com fork
        acho que eu seguiria por esse caminho
  • Está sendo avaliada a ideia de “um cache global compartilhado por todas as instâncias do bundler”
    No longo prazo, isso parece ter um grande potencial, mas ainda estão analisando se há complexidades ocultas
    Issue relacionada: rubygems #7249

    • Não é totalmente simples, mas olhando para os precedentes em outros ecossistemas, parece perfeitamente viável
      O Ruby não é o primeiro a resolver esse problema, então já está na hora de aproveitar esse benefício
  • O princípio básico da otimização é simples — não fazer nada é o mais rápido

    • É preciso abandonar a ilusão de que “código inteligente é rápido”
      A verdadeira otimização é simplesmente não fazer trabalho desnecessário