- L’installation de paquets de Bun fonctionne à une vitesse très élevée par rapport aux gestionnaires de paquets existants
- Le cœur de cette rapidité repose sur une approche relevant de la programmation système et sur la minimisation des appels système
- Les performances sont améliorées grâce à des stratégies fines, comme du code natif basé sur le langage Zig, l’utilisation d’un cache binaire et des optimisations selon l’OS
- Même dans les étapes de décompression des tarballs et de copie de fichiers, des méthodes hautes performances tirant parti des caractéristiques matérielles ont été adoptées
- L’optimisation des structures de données comme le graphe de dépendances et le lockfile améliore l’efficacité du cache CPU et l’accessibilité mémoire
Pourquoi Bun Install est rapide
- En moyenne,
bun install de Bun offre des performances d’installation de paquets 7 fois plus rapides que npm, 4 fois plus rapides que pnpm et 17 fois plus rapides que yarn
- Ce n’est pas simplement le résultat d’un benchmark, mais d’une approche du problème de l’installation de paquets sous l’angle de la programmation système plutôt que de JavaScript
- Bun applique activement des optimisations à plusieurs niveaux : minimisation des appels système, cache binaire des manifests, optimisation de l’extraction des tarballs et copie de fichiers native selon l’OS
Les limites de Node.js et de l’architecture des gestionnaires de paquets
- Depuis la sortie de Node.js en 2009, le modèle d’IO asynchrone fondé sur une boucle d’événements et un pool de threads s’est propagé aux gestionnaires de paquets
- À l’époque, en raison des limites matérielles (disques lents, réseau lent), la stratégie fondée sur l’IO asynchrone et une forte fréquence d’appels système était rationnelle
- Mais sur les systèmes modernes, les SSD NVMe, les réseaux rapides et les CPU haute performance sont devenus courants, et le véritable goulet d’étranglement n’est plus l’IO mais le surcoût des appels système
Le coût des appels système et des changements de mode
- Lorsqu’un programme demande une opération comme la lecture d’un fichier, il doit passer du user mode au kernel mode, un processus qui consomme un nombre élevé de cycles CPU (1000 à 1500 cycles)
- L’installation de paquets nécessite fondamentalement des dizaines de milliers, voire des centaines de milliers d’appels système, si bien que le seul coût de ces transitions représente déjà plusieurs secondes de temps CPU
- Par exemple, lors de l’installation de React et de ses dépendances, npm utilise environ 1 million d’appels système, yarn 4 millions, pnpm 500 000, et bun 160 000
Différences d’approche entre les gestionnaires de paquets existants et Bun
- npm, pnpm et yarn sont tous basés sur Node.js, ce qui oblige à exécuter JavaScript à travers plusieurs couches d’abstraction (libuv, boucle d’événements, pool de threads, médiation des appels système)
- Cela accumule la conversion d’arguments, les files d’attente du pool de workers, la répartition des tâches dans la boucle d’événements et les appels système futex (synchronisation par verrou), au point que la gestion des appels système finit par être plus lente que l’IO elle-même
- À cause de cette limite structurelle, un gestionnaire de paquets construit avec Node.js a du mal à atteindre des performances réellement proches du natif
Bun : un moteur d’installation natif implémenté en Zig
- Bun appelle directement les appels système en langage Zig, en éliminant entièrement le moteur JavaScript et les couches d’abstraction
- Par exemple, la lecture de fichier exécute directement l’appel système
openat() dans le code Zig et renvoie immédiatement les données
- Ainsi, la lecture de dizaines de milliers de fichiers fonctionne à très grande vitesse, sans pool de threads, boucle d’événements ni conversion de données
- Dans les benchmarks, Bun peut lire 146 057
package.json par seconde, tandis que Node.js est plus de deux fois plus lent, autour de 60 000
Gestion des dépendances et optimisation DNS
- Lors de l’exécution de
bun install, Bun déclenche l’analyse des dépendances et le DNS prefetch de manière asynchrone en parallèle
- Par exemple, sur macOS, il utilise l’API DNS asynchrone non officielle d’Apple (
getaddrinfo_async_start()), ce qui permet de traiter simultanément les opérations réseau sans bloquer de thread
- Les gestionnaires de paquets existants reposent sur le pool de threads de libuv, où du code bloquant s’exécute en réalité en interne, ce qui gaspille des ressources
Cache binaire des manifests de paquets
- npm et d’autres mettent les manifests en cache au format JSON, mais Bun les parse une fois, puis stocke le résultat converti en binaire (fichier
.npm)
- Cela réduit au minimum la duplication des chaînes et le coût du parsing, et en mémoire l’accès aux valeurs se fait immédiatement par simple calcul d’offset (sans création de nouveaux objets, parsing ou garbage collection)
- Les en-têtes ETag et If-None-Match permettent de vérifier uniquement les changements et de valider l’actualité des données sans parsing inutile
- D’après les benchmarks, une installation depuis le cache de Bun est plus rapide qu’une fresh install avec npm
Performances de traitement des tarballs
- Les gestionnaires de paquets classiques reçoivent généralement les tarballs en flux, ce qui entraîne une succession de réallocations, copies et redimensionnements dès que la mémoire tampon devient insuffisante
- Bun reçoit d’abord l’intégralité du tarball puis le décompresse ; il détermine à l’avance la taille décompressée grâce aux 4 derniers octets du gzip, puis n’alloue la mémoire qu’une seule fois
- En utilisant notamment libdeflate, Bun accélère la décompression tout en supprimant les copies redondantes et les redimensionnements inutiles
Optimisation du graphe de dépendances et des structures de données
- Les gestionnaires de paquets existants construisent des arbres de dépendances à base d’objets JavaScript et de pointeurs, ce qui disperse aléatoirement la mémoire et provoque fréquemment des défauts de cache CPU (problème de pointer chasing)
- Bun applique le modèle Structure of Arrays (SoA) et stocke tous les paquets, chaînes et dépendances dans de grands blocs de mémoire contigus
- Grâce à un accès fondé sur les offsets et longueurs, le CPU peut lire plusieurs paquets en une fois au niveau des lignes de cache (structure favorable au cache)
- Le lockfile aussi, au lieu d’utiliser JSON/YAML, est stocké suivant le modèle SoA, ce qui facilite l’élimination des doublons de chaînes et l’accès séquentiel en mémoire
- Un format binaire de lockfile (
bun.lockb) a également été introduit à titre expérimental, mais a été abandonné au profit d’un format texte plus lisible, car il dégradait la collaboration via Git
Optimisation de la copie de fichiers selon l’OS
macOS
- Utilisation de
clonefile : duplication d’un répertoire entier en Copy-On-Write via un seul appel système
- Cela minimise l’usage redondant de l’espace disque et maximise la vitesse d’installation
- En cas d’échec de
clonefile, Bun applique un fallback progressif : clonage par répertoire, puis copyfile
Linux
- Tentative prioritaire de hardlink : au lieu de créer un nouveau fichier, seul un nouveau pointeur vers le fichier existant est créé (sans déplacement des données sur le disque)
- Si le hardlink n’est pas possible, Bun applique le Copy-On-Write via
ioctl_ficlone sur Btrfs/XFS
- Ensuite, il bascule si nécessaire vers
copy_file_range, sendfile, puis en dernier recours vers une copie classique avec copyfile
Bilan
- Bun dépasse les limites traditionnelles de performance des gestionnaires de paquets grâce à la minimisation des appels système, aux structures binaires, aux optimisations spécifiques à l’OS et à l’amélioration des structures de données
- Cela apporte non seulement des installations ultra-rapides, mais améliore aussi l’efficacité mémoire et CPU
- Par rapport aux gestionnaires basés sur Node.js, il peut être adopté dans les projets sans remplacement de runtime distinct, tout en conservant la compatibilité
- Dans de grandes bases de code réelles, il offre une expérience où des installations qui prenaient plusieurs minutes sont réduites à quelques millisecondes à quelques secondes
- C’est un excellent exemple d’optimisation sur mesure adaptée au système, au matériel et à l’OS, avec une forte valeur d’étude et de référence
Aucun commentaire pour le moment.