- 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
Falar é fácil. Quero ver o código!
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-pythonnão é por desempenho, e sim para uma resolução de dependências melhorPor 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 4O 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
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
Esse ambiente caótico com várias ferramentas de gerenciamento de versão e versões do Ruby misturadas é realmente frustrante
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
requireSe 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
Hoje deve estar ultrapassado, mas ainda tenho carinho por esse mini projeto
Código: fastup
O problema real é a estrutura em que o
$LOAD_PATHadiciona todos os gems e causa uma explosão combinatóriaO 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
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
Ex.: uma estrutura contendo apenas versão, dependências e hash
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
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
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
A verdadeira otimização é simplesmente não fazer trabalho desnecessário