- Die Paketinstallation von Bun arbeitet im Vergleich zu bestehenden Paketmanagern extrem schnell
- Der Schlüssel zur schnellen Installation liegt in einem systemprogrammatischen Ansatz und der Minimierung von Systemaufrufen
- Leistungssteigerungen werden durch detaillierte Strategien wie native Implementierung auf Basis von Zig, die Nutzung von Binärcaches und OS-spezifische Optimierungen erzielt
- Auch bei der Entpackung von Tarballs und dem Kopieren von Dateien kommen Hochleistungsverfahren zum Einsatz, die Hardware-Eigenschaften ausnutzen
- Durch Optimierung von Datenstrukturen wie Abhängigkeitsgraphen und Lockfiles werden CPU-Cache-Effizienz und Speicherzugänglichkeit verbessert
Warum Bun Install schnell ist
bun install von Bun bietet im Durchschnitt eine 7-fach schnellere Paketinstallation als npm, 4-fach schneller als pnpm und 17-fach schneller als yarn
- Das liegt nicht nur an Benchmarks, sondern daran, dass das Problem der Paketinstallation aus der Perspektive der Systemprogrammierung statt aus der von JavaScript angegangen wurde
- Auf mehreren Ebenen werden Leistungsoptimierungen konsequent umgesetzt, darunter die Minimierung von Systemaufrufen, binäres Caching von Manifesten, optimierte Tarball-Extraktion und natives Dateikopieren des Betriebssystems
Grenzen von Node.js und der Architektur von Paketmanagern
- Seit der Veröffentlichung von Node.js im Jahr 2009 hat sich das asynchrone IO-Modell auf Basis von Event Loop und Threadpool auch auf Paketmanager übertragen
- Damals war diese Strategie mit asynchronem IO und hoher Frequenz an Systemaufrufen aufgrund der Hardwaregrenzen (langsame Festplatten, langsame Netzwerke) sinnvoll
- In modernen Systemen sind jedoch NVMe-SSDs, schnelle Netzwerke und leistungsstarke CPUs verbreitet, und der eigentliche Flaschenhals ist nicht IO, sondern der Overhead von Systemaufrufen
Die Kosten von Systemaufrufen und Moduswechseln
- Wenn ein Programm eine Aufgabe wie das Lesen einer Datei anfordert, muss es von User Mode in den Kernel Mode wechseln, was teure CPU-Zyklen verbraucht (1000–1500 cycles)
- Paketinstallationen erfordern grundsätzlich zehntausende bis hunderttausende Systemaufrufe, sodass allein die Kosten dieser Umschaltungen bereits mehrere Sekunden CPU-Zeit verbrauchen können
- Bei der Installation von React und seinen Abhängigkeiten verwendet npm zum Beispiel rund 1 Million, yarn 4 Millionen, pnpm 500.000 und bun 160.000 Systemaufrufe
Unterschiede zwischen bestehenden Paketmanagern und dem Ansatz von Bun
- npm, pnpm und yarn basieren alle auf Node.js, wodurch JavaScript durch mehrere Abstraktionsschichten ausgeführt werden muss (libuv, Event Loop, Threadpool, Vermittlung von Systemaufrufen)
- Dabei summieren sich Argumentkonvertierung, Workerpool-Warteschlangen, Verzweigung von Event-Loop-Aufgaben und
futex-Systemaufrufe zur Locksynchronisation, sodass am Ende die Verwaltung der Systemaufrufe sogar langsamer wird als das IO selbst
- Paketmanager, die mit Node.js gebaut sind, können aufgrund dieser strukturellen Grenzen kaum eine tatsächlich native Leistung erreichen
Bun: eine native Installations-Engine in Zig
- Bun ruft in der Sprache Zig Systemaufrufe direkt auf und lässt damit sowohl die JavaScript-Engine als auch sämtliche Abstraktionsschichten aus
- Beim Lesen einer Datei wird zum Beispiel direkt aus Zig-Code der Systemaufruf
openat() ausgeführt und die Daten sofort zurückgegeben
- Dadurch kann das Lesen von zehntausenden Dateien ohne separaten Threadpool, Event Loop oder Datenkonvertierung extrem schnell ablaufen
- In Benchmarks kann Bun 146.057
package.json-Dateien pro Sekunde lesen; Node.js ist mit rund 60.000 mehr als doppelt so langsam
Optimierung von Abhängigkeitsverwaltung und DNS
- Beim Ausführen von
bun install stößt Bun parallel zur Analyse der Abhängigkeiten asynchron DNS-Prefetching an
- Auf macOS wird dafür zum Beispiel Apples inoffizielle asynchrone DNS-API (
getaddrinfo_async_start()) verwendet, was die gleichzeitige Ausführung von Netzwerkaufgaben ohne blockierende Threads ermöglicht
- Bestehende Paketmanager basieren auf dem libuv-Threadpool, wodurch intern faktisch blockierender Code läuft und Ressourcen verschwendet werden
Binäres Caching von Paketmanifesten
- npm und andere cachen Manifeste als JSON, Bun hingegen parst sie einmal und speichert das Ergebnis anschließend als Binärformat (
.npm-Datei)
- Dadurch werden String-Duplikate und Parsing-Overhead minimiert; im Arbeitsspeicher ist der direkte Zugriff über Offset-Berechnungen möglich (keine neuen Objekte, kein erneutes Parsing, keine Garbage Collection nötig)
- Mit ETag- und
If-None-Match-Headern werden nur Änderungen geprüft, sodass die Aktualität ohne unnötiges Parsen von Daten verifiziert werden kann
- In Benchmarks ist eine Installation aus dem Bun-Cache sogar schneller als eine frische Installation mit npm
Performance bei der Verarbeitung von Tarballs
- Herkömmliche Paketmanager empfangen Tarballs als Stream, wodurch bei unzureichendem Pufferspeicher fortlaufend Reallokationen, Kopien und Größenanpassungen auftreten
- Bun empfängt zuerst den gesamten Tarball und entpackt ihn dann; über die letzten 4 Bytes von gzip wird die unkomprimierte Größe vorab bestimmt, sodass nur ein einziges Mal Speicher allokiert werden muss
- Mit
libdeflate und ähnlichen Bibliotheken wird schnelles Entpacken ermöglicht und sowohl unnötige Kopien als auch Größenanpassungen vollständig vermieden
Abhängigkeitsgraphen und Optimierung von Datenstrukturen
- Bestehende Paketmanager erzeugen Abhängigkeitsbäume auf Basis von JavaScript-Objekten und Pointern, was zu zufälliger Verteilung im Speicher und häufigen CPU-Cache-Misses führt (Problem des Pointer Chasing)
- Bun verwendet das Muster Structure of Arrays (SoA) und speichert alle Pakete, Strings und Abhängigkeiten in großen zusammenhängenden Speicherblöcken
- Durch den Zugriff auf Basis von Offsets und Längen kann die CPU mehrere Pakete gleichzeitig in Cache-Line-Einheiten lesen (cachefreundliche Struktur)
- Auch das Lockfile wird statt als JSON/YAML passend zum SoA-Muster so gespeichert, dass String-Deduplizierung und sequenzieller Speicherzugriff leicht möglich sind
- Das Binärformat des Lockfiles (
bun.lockb) wurde ebenfalls experimentell eingeführt, wegen der schlechteren Zusammenarbeit über Git jedoch durch ein besser lesbares Plain-Format ersetzt
OS-spezifische Optimierung beim Kopieren von Dateien
macOS
- Verwendung von
clonefile: Ein Verzeichnis wird als Ganzes per Copy-on-Write mit einem einzigen Systemaufruf dupliziert
- Dadurch wird die doppelte Nutzung von Speicherplatz auf dem Datenträger minimiert und die Installationsgeschwindigkeit maximiert
- Falls
clonefile fehlschlägt, erfolgt ein stufenweiser Fallback über per-directory cloning zu copyfile
Linux
- Zuerst wird ein Hardlink versucht: Es wird keine neue Datei erzeugt, sondern nur eine neue Referenz auf die bestehende Datei angelegt (ohne Verschiebung von Datenträgerdaten)
- Wenn Hardlinks nicht möglich sind, wird auf Btrfs/XFS per
ioctl_ficlone Copy-on-Write verwendet
- Danach folgen als Fallback
copy_file_range, sendfile und zuletzt normales copyfile
Gesamtfazit
- Bun überschreitet durch Minimierung von Systemaufrufen, binäre Strukturen, OS-Optimierung und verbesserte Datenstrukturen die traditionellen Leistungsgrenzen von Paketmanagern
- Dadurch werden neben extrem schnellen Installationen auch Speicher- und CPU-Effizienz verbessert
- Im Vergleich zu bestehenden Node.js-basierten Managern kann es ohne Austausch der Laufzeit direkt in Projekten eingesetzt werden (Kompatibilität bleibt erhalten)
- In realen großen Codebasen verkürzt es Installationsvorgänge, die früher mehrere Minuten dauerten, auf wenige Millisekunden bis einige Sekunden
- Als gelungenes Beispiel für maßgeschneiderte Optimierung auf System-, Hardware- und OS-Ebene hat es hohen Forschungs- und Referenzwert
Noch keine Kommentare.