- Analiza los límites de rendimiento de Bundler y compara por qué el gestor de paquetes de Python uv es rápido
- La velocidad de uv no se debe al lenguaje Rust, sino a su diseño estructural, como descargas en paralelo, caché global y manejo de dependencias basado en metadatos
- Bundler tiene acoplados los procesos de descarga e instalación, lo que limita el procesamiento en paralelo; si se separan, hay un gran margen de mejora
- Se puede reducir la duplicación entre RubyGems y Bundler mediante integración de caché global, instalación con hardlinks e integración del resolvedor PubGrub
- Incluso sin reescribir el lenguaje, la mayor parte de las mejoras de rendimiento puede lograrse dentro del código Ruby, acercándose a velocidades del nivel de uv
Comparación de rendimiento entre Bundler y uv
- A partir de la pregunta planteada en RailsWorld, “¿por qué Bundler no es tan rápido como uv?”, se investigan los cuellos de botella de rendimiento de Bundler
- El autor afirma con convicción que Bundler puede alcanzar velocidades del nivel de uv, y deja claro que la diferencia de rendimiento no es un problema del lenguaje, sino del diseño
- Citando el artículo de Andrew Nesbitt “How uv got so fast”, analiza si las optimizaciones clave de uv pueden aplicarse a Bundler
¿Hace falta reescribirlo en Rust?
- Es cierto que uv está escrito en Rust, pero la causa esencial de su velocidad no es Rust en sí
- Si al eliminar los cuellos de botella de Bundler se llegara al punto en que “la única mejora restante sería reescribirlo en Rust”, eso se consideraría un éxito
- Reescribirlo en Rust ofrece libertad para probar diseños experimentales sin las restricciones de compatibilidad existentes, pero no es un requisito indispensable
Cuellos de botella estructurales de Bundler
- Bundler combina la descarga e instalación de gems en un solo método, lo que impide las descargas en paralelo
- En el código de ejemplo, el método
install ejecuta fetch_gem_if_not_cached y luego install de forma consecutiva
- Por eso, gems con relaciones de dependencia (
a -> b -> c) solo pueden instalarse de manera secuencial
- En experimentos, cuando hay dependencias tarda más de 9 segundos, pero gems independientes (
d, e, f) se completan en menos de 4 segundos con descargas en paralelo
- Si se separan la descarga y la instalación, se puede procesar en paralelo sin romper las reglas de dependencia
- Se propone separar en cuatro etapas (descarga → descompresión → compilación → instalación)
- En gems Ruby puros, relajar el orden de instalación de dependencias podría aportar mejoras adicionales de velocidad
Optimización de caché e instalación
- El enfoque de uv de caché global e instalación con hardlinks también puede aplicarse a Bundler
- Actualmente, Bundler y RubyGems usan cachés separadas según la versión de Ruby
- Hace falta integrarlas en una caché compartida basada en
$XDG_CACHE_HOME
- La instalación con hardlinks podría aplicarse después de unificar la caché
- Bundler ya usa el resolvedor de dependencias PubGrub, pero RubyGems todavía usa molinillo
- La integración de ambos resolvedores es clave para eliminar deuda técnica
Qué optimizaciones asociadas a Rust sí pueden aplicarse
- La deserialización zero-copy podría aplicarse parcialmente en la etapa de parsing YAML de RubyGems
- El GVL (Global VM Lock) de Ruby no representa una gran limitación para el paralelismo en tareas centradas en IO
- El procesamiento de IO y ZLIB libera el GVL, por lo que pueden ejecutarse en paralelo
- Sin embargo, al escribir archivos pequeños, la sobrecarga de gestionar el GVL sí reduce el rendimiento
- Ya se está trabajando en mejorar esto dentro de Ruby
- Optimización de comparación de versiones: uv acelera estas comparaciones codificando versiones como enteros
u64
- En Ruby también podría mejorarse el rendimiento del resolvedor convirtiendo
Gem::Version a una representación basada en enteros
- Ya hubo intentos de refactorización relacionados, pero se dejaron en pausa por problemas de compatibilidad hacia atrás
Conclusión y próximos pasos
- La velocidad de uv se debe más a un diseño que elimina trabajo innecesario que al lenguaje, y Bundler también puede mejorar en esa misma dirección
- RubyGems y Bundler ya cuentan con una estructura moderna de gestión de paquetes, por lo que alcanzar velocidades del nivel de uv es realista
- El mayor desafío es mantener compatibilidad con código legado
- Incluso sin reescribirlo en Rust, el 99% de la mejora de rendimiento puede lograrse dentro del código Ruby; el 1% restante sería marginal
- En una publicación posterior se abordarán el profiling real de Bundler y RubyGems, y las causas concretas de sus cuellos de botella
2 comentarios
¡Hablar es barato. Muéstrame el código!
Comentarios en Hacker News
No conozco a fondo la estructura de Bundler, pero creo que la mayor mejora sería adoptar el diseño de caché de uv
Una parte clave de por qué uv es rápido está en su estructura de caché, y eso se puede replicar en otros lenguajes o ecosistemas
Aun así, la parte de ignorar el límite superior de
requires-pythonno es por rendimiento, sino para una mejor resolución de dependenciasPor ejemplo, si un proyecto requiere Python 3.8 o superior, pero alguna dependencia impone la restricción
<4, entonces no se podría instalar en Python 4uv resuelve para todas las versiones compatibles, así que ignorar el límite superior casi no ahorra tiempo
La discusión relacionada puede verse en el foro Python Discuss
Después de PEP 658, la Simple Repository API de Python entrega los metadatos directamente, y RubyGems.org ya ofrece información parecida
Pero solo al descomprimir un gem se puede saber si tiene extensiones nativas
Por eso se propone que esta información se agregue directamente a los metadatos de RubyGems.org, para poder paralelizar por completo el árbol de instalación de dependencias
Recuerdo que cuando trabajaba en RubyGems.org, los metadatos se extraían por versión
Habría que volver a procesar los gemspec de versiones antiguas, y eso podría ser un cambio riesgoso de metadatos
Así que quizá sea difícil aplicarlo a versiones anteriores, pero hacia adelante sí podría mejorarse para conocer el orden de instalación sin necesidad de unpack
Me gusta que Aaron se enfoque en mejoras algorítmicas reales en lugar de reescribir Bundler en Rust
Ese entorno confuso donde se mezclan varias herramientas de gestión de versiones y distintas versiones de Ruby es realmente desesperante
Creo que el problema no es solo la velocidad, sino también el control y la dirección del ecosistema
Ruby se ha enfocado en la velocidad durante los últimos 10 años, pero la calidad de la documentación y la gestión de la comunidad eran todavía más importantes
Ya es momento de pensar en serio por qué el lenguaje está perdiendo fuerza y empujar ideas diversas
Como lectura relacionada reciente está How uv got so fast (diciembre de 2025, 457 comentarios)
Para hacer RubyGems más rápido, la clave es registrar o guardar en una base de datos la lista de archivos de cada gem
Así no haría falta escanear el sistema de archivos cada vez que se ejecuta
requireSi se modifica un gem directamente, habría que volver a hacer hash de los metadatos, pero de todos modos no se recomienda editarlo manualmente
Ahora ya estará anticuado, pero sigue siendo un miniproyecto al que le tengo cariño
Código: fastup
bundle installes atacar el problema por el lado equivocadoEl problema real es que
$LOAD_PATHagrega todos los gem y provoca una explosión combinatoriaEl hecho de que existan varios proyectos de caché demuestra que este sí es un problema real
Antes una app tardaba minutos en arrancar, y una vez logré reducirla en minutos manipulando el load path
Antes propuse integrar bootsnap en bundler, pero lo rechazaron
Me pareció interesante la explicación de la estructura de RubyGems
Un gem es un archivo tar, y dentro lleva un YAML GemSpec que declara las dependencias
RubyGems.org expone esa información por API, así que se pueden revisar dependencias sin usar eval
Aun así, YAML es un formato poco eficiente de parsear, y quizá una alternativa como JSON o protobuf sería mejor
Pero si gemserver ya devuelve la información de dependencias, probablemente no sea un gran problema
Por ejemplo, una estructura que incluya solo versión, dependencias y hash
Esa también es una razón de por qué uv es rápido: puede calcular dependencias sin descargar el paquete
Hace tiempo hice un video prototipo sobre cómo debería funcionar la instalación de gems
how_gems_should_be.mov
Las fibers de Ruby (o la librería Async) suelen estar sobrevaloradas
Igual que con los threads, siguen existiendo problemas de coordinación de nivel superior, como los pools de conexiones
Aun así, si las tareas de instalación ligadas a IO se manejan de forma asíncrona, se puede lograr una mejora de rendimiento significativa
Creo que iría por un enfoque así
Se está evaluando la idea de que una caché global sea compartida por todas las instancias de bundler
A largo plazo parece que daría una gran ventaja, pero se está revisando si hay complejidades ocultas
Issue relacionado: rubygems #7249
Ruby no es el primero en resolver este problema, así que ya va siendo hora de aprovechar esos beneficios
El principio básico de la optimización es simple: no hacer nada es lo más rápido
La verdadera optimización es evitar hacer trabajo innecesario