- La instalación de paquetes de Bun funciona a una velocidad muy superior en comparación con los gestores de paquetes existentes
- La clave de su rapidez está en un enfoque desde la programación de sistemas y en la minimización de llamadas al sistema
- Ofrece mejoras de rendimiento mediante estrategias detalladas como código nativo basado en Zig, uso de caché binaria y optimizaciones por sistema operativo
- Incluso en el proceso de descompresión de tarballs y copia de archivos, adopta métodos de alto rendimiento que aprovechan las características del hardware
- Mediante la optimización de estructuras de datos como el grafo de dependencias y el lockfile, mejora la eficiencia de la caché de CPU y la accesibilidad a la memoria
Por qué Bun Install es rápido
bun install de Bun ofrece, en promedio, un rendimiento de instalación de paquetes 7 veces más rápido que npm, 4 veces más rápido que pnpm y 17 veces más rápido que yarn
- Esto no se debe solo a un benchmark, sino a que el problema de instalar paquetes fue abordado desde la perspectiva de la programación de sistemas y no desde JavaScript
- Aplica agresivamente optimizaciones de rendimiento en múltiples capas, como minimización de llamadas al sistema, caché binaria de manifiestos, optimización de extracción de tarballs y copia de archivos nativa del sistema operativo
Límites de Node.js y de la arquitectura de los gestores de paquetes
- Desde el lanzamiento de Node.js en 2009, el modelo de IO asíncrono basado en event loop y thread pool también se trasladó a los gestores de paquetes
- En ese momento, debido a las limitaciones del hardware de la época (discos lentos, red lenta), la estrategia de IO asíncrono y alta frecuencia de llamadas al sistema era razonable
- Sin embargo, en los sistemas modernos ya son comunes los SSD NVMe, redes rápidas y CPUs de alto rendimiento, y el verdadero cuello de botella ya no es el IO, sino el overhead de las llamadas al sistema
El costo de las llamadas al sistema y del cambio de modo
- Cuando un programa solicita una operación como leer un archivo, debe cambiar de user mode a kernel mode, y este proceso consume costosos ciclos de CPU (1000~1500 cycles)
- La instalación de paquetes requiere por naturaleza decenas de miles o incluso cientos de miles de llamadas al sistema, por lo que solo el costo de esos cambios puede consumir varios segundos de tiempo de CPU
- Por ejemplo, al instalar React y sus dependencias, npm usa alrededor de 1 millón de llamadas al sistema, yarn 4 millones, pnpm 500 mil y bun 160 mil
Diferencias de enfoque entre los gestores de paquetes existentes y Bun
- npm, pnpm y yarn están todos basados en Node.js, por lo que JavaScript debe ejecutarse a través de varias capas de abstracción (libuv, event loop, thread pool, intermediación de llamadas al sistema)
- En este proceso se acumulan conversiones de argumentos, colas del worker pool, bifurcaciones de tareas del event loop y llamadas al sistema futex (sincronización de locks), lo que termina haciendo que la gestión de llamadas al sistema sea más lenta que el propio IO
- Un gestor de paquetes hecho con Node.js tiene dificultades para alcanzar un rendimiento cercano al nativo debido a estas limitaciones estructurales
Bun: un motor de instalación nativo implementado en Zig
- Bun, implementado en Zig, invoca directamente las llamadas al sistema, omitiendo por completo el motor de JavaScript y las capas de abstracción
- Por ejemplo, la lectura de archivos ejecuta directamente la llamada al sistema openat() desde código Zig y devuelve los datos de inmediato
- Por eso, el proceso de leer decenas de miles de archivos funciona a altísima velocidad sin pasar por thread pools, event loops ni transformaciones de datos adicionales
- Según benchmarks, Bun puede leer 146,057
package.json por segundo, mientras que Node.js es más de 2 veces más lento, en el rango de 60 mil
Gestión de dependencias y optimización de DNS
- Al ejecutar
bun install, Bun dispara el análisis de dependencias y el DNS prefetch de manera asíncrona al mismo tiempo
- Por ejemplo, en macOS usa la API async DNS no oficial de Apple (
getaddrinfo_async_start()), lo que permite procesar trabajo de red en paralelo sin bloquear threads
- Los gestores de paquetes existentes, al basarse en el thread pool de libuv, terminan ejecutando internamente código bloqueante y desperdiciando recursos
Caché binaria de manifiestos de paquetes
- npm y otros almacenan en caché los manifiestos como JSON, pero Bun los parsea una vez y luego guarda el resultado convertido en binario (archivo
.npm)
- Esto minimiza la duplicación de strings y el overhead de parsing, y en memoria real permite acceder a los valores directamente con cálculos de offsets (sin crear nuevos objetos, sin parsing y sin garbage collection)
- Con los headers ETag e If-None-Match puede verificar solo los cambios y validar actualidad sin parsear datos innecesarios
- En benchmarks, la instalación desde caché de Bun es incluso más rápida que una instalación fresh de npm
Rendimiento en el procesamiento de tarballs (archivos comprimidos)
- Los gestores de paquetes normales reciben el tarball como stream, y cuando falta memoria de buffer se producen continuamente reasignaciones, copias y redimensionamientos
- Bun recibe el tarball completo antes de descomprimirlo y usa los últimos 4 bytes del gzip para conocer de antemano el tamaño descomprimido, por lo que solo asigna memoria una vez
- Aprovecha
libdeflate y otras técnicas para descompresión rápida, eliminando copias duplicadas innecesarias y cambios de tamaño
Optimización del grafo de dependencias y de las estructuras de datos
- Los gestores de paquetes existentes construyen árboles de dependencias basados en objetos y punteros de JavaScript, lo que dispersa aleatoriamente la memoria y provoca frecuentes fallos de caché de CPU (problema de pointer chasing)
- Bun aplica el patrón Structure of Arrays (SoA) para guardar todos los paquetes, strings y dependencias en grandes bloques continuos de memoria
- Con acceso basado en offsets/longitudes, la CPU puede leer varios paquetes de una vez en unidades de cache line (estructura amigable con la caché)
- El lockfile también se almacena, en lugar de JSON/YAML, de forma alineada con el patrón SoA, eliminando duplicación de strings y facilitando el acceso secuencial a memoria
- También introdujo de forma experimental el formato binario de lockfile (
bun.lockb), pero luego cambió a un formato de texto plano más legible debido al deterioro en la colaboración con Git
Optimización de copia de archivos por sistema operativo
macOS
- Uso de
clonefile: clona directorios completos en una sola llamada al sistema usando Copy-On-Write
- Minimiza el uso duplicado de espacio en disco y maximiza la velocidad de instalación
- Si
clonefile falla, aplica un fallback escalonado: clonación por directorio y luego copyfile
Linux
- Intenta primero hard links: crea una nueva referencia al archivo existente sin generar un archivo nuevo (sin mover datos en disco)
- Si no se pueden usar hard links, en Btrfs/XFS aplica Copy-On-Write mediante
ioctl_ficlone
- Después hace fallback a
copy_file_range, sendfile y finalmente a la copia tradicional con copyfile
Conclusión
- Bun superó los límites tradicionales de rendimiento de los gestores de paquetes mediante minimización de llamadas al sistema, estructuras binarias, optimización por sistema operativo y mejoras en estructuras de datos
- Gracias a ello, además de instalaciones ultrarrápidas, también ofrece mejoras en eficiencia de memoria y CPU
- Puede aplicarse a proyectos sin reemplazar por completo el runtime frente a gestores basados en Node.js (manteniendo compatibilidad)
- En bases de código grandes, ofrece la experiencia de reducir procesos de instalación que antes tardaban minutos a unos pocos milisegundos o segundos
- Es un excelente caso de optimización a medida según el sistema, el hardware y el nivel del sistema operativo, con alto valor como referencia e investigación
Aún no hay comentarios.