Real-project benchmarks
Cold, warm and no-op installs plus physical disk for fnpm, Bun, pnpm, Yarn and npm on pinned open-source repositories, each followed by a project check.
Every repository is installed by fnpm, Bun, pnpm, Yarn and npm. Each bar is the median of three runs, in seconds, including process startup; hover or focus a bar for the exact value and the min–max range. Timed installs disable lifecycle scripts. After the timings, each tool’s tree runs the repository’s own check (a build, a type check or tests) listed under its name.
How each tool gets its lockfile
The repository’s committed lockfile decides what a tool can reuse:
- Own lockfile: the tool reads the repository’s lockfile as is (pnpm with
pnpm-lock.yaml, Yarn withyarn.lock, npm withpackage-lock.json). - Migrated: fnpm and Bun read every format and migrate it, keeping the locked versions.
- Imported lock: pnpm converts a
yarn.lockorpackage-lock.jsonwithpnpm import. - Fresh resolve: the tool has no way to read the repository’s lockfile, so it resolves the manifests’ ranges itself. Its package versions can differ; the version counts under each repository show by how much.
The first install that creates the tool’s own lockfile is untimed. Every timed run then starts from that lockfile: cold with an empty private cache and no node_modules, warm with the cache kept and node_modules removed, no-op with both kept. A tool that cannot install a repository at all keeps its row with the reason, and gets no time.
Summary
Measurement in progress: 2 repositories are complete for all five tools; OpenCode, Excalidraw, Jest, React, Vite, Vue core, webpack, Uptime Kuma are still running and appear here once every tool has finished.
Measured October 26, 2026 on Linux x64 (8 CPU cores, Node v24.22.1), fnpm built from 5e5b347. Each cell counts the repositories where fnpm's median was lower than the other tool's, out of the repositories where both installed.
| fnpm lower than | Bun | pnpm | Yarn | npm |
|---|---|---|---|---|
| Cold install | 1 / 2 | 2 / 2 | — | — |
| Warm cache | 1 / 2 | 2 / 2 | — | — |
| No-op | 2 / 2 | 2 / 2 | — | — |
| Disk | 2 / 2 | 1 / 2 | — | — |
Could not install: yarn on OpenNext Cloudflare, npm on OpenNext Cloudflare, yarn on tRPC, npm on tRPC. These pairs keep their rows in the charts, without a value.
Every completed install passed its repository's project check.
Repositories
OpenNext Cloudflare
Cold install
Empty package cache · no node_modules · prepared lockfile
View data table
| Tool / version | Seconds | Lockfile |
|---|---|---|
| fnpm 0.0.2 | 5.5416 | migrated from pnpm-lock.yaml |
| bun 1.4.4 | 5.6774 | migrated from pnpm-lock.yaml |
| pnpm 10.33.0 | 15.2390 | the project's own pnpm-lock.yaml |
| yarn 1.22.22 | — | yarn cannot read catalog: dependency specifiers |
| npm 11.19.1 | — | npm cannot read workspace: or catalog: dependency specifiers |
1.02× faster than Bun in this snapshot
Warm cache
Packages already cached · node_modules rebuilt
View data table
| Tool / version | Seconds | Lockfile |
|---|---|---|
| fnpm 0.0.2 | 0.7809 | migrated from pnpm-lock.yaml |
| bun 1.4.4 | 0.9632 | migrated from pnpm-lock.yaml |
| pnpm 10.33.0 | 6.1904 | the project's own pnpm-lock.yaml |
| yarn 1.22.22 | — | yarn cannot read catalog: dependency specifiers |
| npm 11.19.1 | — | npm cannot read workspace: or catalog: dependency specifiers |
1.23× faster than Bun in this snapshot
No-op
Keep cache and node_modules · reinstall after nothing changed
View data table
| Tool / version | Seconds | Lockfile |
|---|---|---|
| fnpm 0.0.2 | 0.0068 | migrated from pnpm-lock.yaml |
| bun 1.4.4 | 0.0888 | migrated from pnpm-lock.yaml |
| pnpm 10.33.0 | 2.6415 | the project's own pnpm-lock.yaml |
| yarn 1.22.22 | — | yarn cannot read catalog: dependency specifiers |
| npm 11.19.1 | — | npm cannot read workspace: or catalog: dependency specifiers |
13.06× faster than Bun in this snapshot
Disk
node_modules + private cache/store · hardlinks counted once
View data table
| Tool / version | MiB | Lockfile |
|---|---|---|
| fnpm 0.0.2 | 2592.96 | migrated from pnpm-lock.yaml |
| bun 1.4.4 | 2653.38 | migrated from pnpm-lock.yaml |
| pnpm 10.33.0 | 2580.19 | the project's own pnpm-lock.yaml |
| yarn 1.22.22 | — | yarn cannot read catalog: dependency specifiers |
| npm 11.19.1 | — | npm cannot read workspace: or catalog: dependency specifiers |
fnpm used 2.3% less disk than Bun
All numbers for OpenNext Cloudflare
| Tool | Lockfile | Cold (s) | Warm (s) | No-op (s) | Disk (MiB) | Versions | vs Bun missing / extra | Check |
|---|---|---|---|---|---|---|---|---|
| fnpm 0.0.2 | migrated from pnpm-lock.yaml | 5.5416 4.7688–5.7745 | 0.7809 0.5714–1.0977 | 0.0068 0.0066–0.0074 | 2592.96 2518.9 + 74.06 | 1651 | 0 / 14 | pass |
| bun 1.4.4 | migrated from pnpm-lock.yaml | 5.6774 4.9622–6.8519 | 0.9632 0.5732–1.1921 | 0.0888 0.0773–0.0928 | 2653.38 2588.28 + 65.1 | 1637 | 0 / 0 | pass |
| pnpm 10.33.0 | the project's own pnpm-lock.yaml | 15.239 14.9222–15.8688 | 6.1904 6.0119–6.3856 | 2.6415 2.636–2.7777 | 2580.19 2449.55 + 130.64 | 1651 | 0 / 14 | pass |
| yarn 1.22.22 | could not install: yarn cannot read catalog: dependency specifiers | — | — | — | — | — | — | — |
| npm 11.19.1 | could not install: npm cannot read workspace: or catalog: dependency specifiers | — | — | — | — | — | — | — |
tRPC
Cold install
Empty package cache · no node_modules · prepared lockfile
View data table
| Tool / version | Seconds | Lockfile |
|---|---|---|
| fnpm 0.0.2 | 5.8114 | migrated from pnpm-lock.yaml |
| bun 1.4.4 | 5.6859 | migrated from pnpm-lock.yaml |
| pnpm 12.4.1 | 140.7165 | the project's own pnpm-lock.yaml |
| yarn 1.22.22 | — | yarn cannot read catalog: dependency specifiers |
| npm 11.19.1 | — | npm cannot read workspace: or catalog: dependency specifiers |
1.02× Bun's duration in this snapshot
Warm cache
Packages already cached · node_modules rebuilt
View data table
| Tool / version | Seconds | Lockfile |
|---|---|---|
| fnpm 0.0.2 | 0.8430 | migrated from pnpm-lock.yaml |
| bun 1.4.4 | 0.7964 | migrated from pnpm-lock.yaml |
| pnpm 12.4.1 | 2.9687 | the project's own pnpm-lock.yaml |
| yarn 1.22.22 | — | yarn cannot read catalog: dependency specifiers |
| npm 11.19.1 | — | npm cannot read workspace: or catalog: dependency specifiers |
1.06× Bun's duration in this snapshot
No-op
Keep cache and node_modules · reinstall after nothing changed
View data table
| Tool / version | Seconds | Lockfile |
|---|---|---|
| fnpm 0.0.2 | 0.0092 | migrated from pnpm-lock.yaml |
| bun 1.4.4 | 0.1780 | migrated from pnpm-lock.yaml |
| pnpm 12.4.1 | 0.3851 | the project's own pnpm-lock.yaml |
| yarn 1.22.22 | — | yarn cannot read catalog: dependency specifiers |
| npm 11.19.1 | — | npm cannot read workspace: or catalog: dependency specifiers |
19.35× faster than Bun in this snapshot
Disk
node_modules + private cache/store · hardlinks counted once
View data table
| Tool / version | MiB | Lockfile |
|---|---|---|
| fnpm 0.0.2 | 2490.89 | migrated from pnpm-lock.yaml |
| bun 1.4.4 | 2558.46 | migrated from pnpm-lock.yaml |
| pnpm 12.4.1 | 3382.89 | the project's own pnpm-lock.yaml |
| yarn 1.22.22 | — | yarn cannot read catalog: dependency specifiers |
| npm 11.19.1 | — | npm cannot read workspace: or catalog: dependency specifiers |
fnpm used 2.6% less disk than Bun
All numbers for tRPC
| Tool | Lockfile | Cold (s) | Warm (s) | No-op (s) | Disk (MiB) | Versions | vs Bun missing / extra | Check |
|---|---|---|---|---|---|---|---|---|
| fnpm 0.0.2 | migrated from pnpm-lock.yaml | 5.8114 5.5396–5.8503 | 0.843 0.7924–0.9986 | 0.0092 0.0089–0.0104 | 2490.89 2382.78 + 108.11 | 3401 | 0 / 0 | pass |
| bun 1.4.4 | migrated from pnpm-lock.yaml | 5.6859 5.2473–6.7252 | 0.7964 0.7392–0.9276 | 0.178 0.1716–0.1824 | 2558.46 2462.47 + 96 | 3401 | 0 / 0 | pass |
| pnpm 12.4.1 | the project's own pnpm-lock.yaml | 140.7165 140.6582–142.5789 | 2.9687 2.8707–3.0206 | 0.3851 0.3794–0.4194 | 3382.89 2361.22 + 1021.67 | 3418 | 0 / 17 | pass |
| yarn 1.22.22 | could not install: yarn cannot read catalog: dependency specifiers | — | — | — | — | — | — | — |
| npm 11.19.1 | could not install: npm cannot read workspace: or catalog: dependency specifiers | — | — | — | — | — | — | — |
Script-enabled OpenNext check
An earlier, separate untimed install of OpenNext Cloudflare with fnpm 0.0.1 used the default trusted-dependency policy on Linux with Node 24.22.1. It completed in 240.16 seconds. Three untrusted scripts remained blocked. better-sqlite3 compiled from source, the root build ran with automatic dependency verification disabled, 37 Vitest files / 364 tests passed, and a real SQL query returned {"value":42}. The original pnpm lock remained unchanged. This does not cover Node 26 on macOS, all native modules or arbitrary lifecycle scripts, and its duration is not part of the script-free medians.
Limits
This is one shared Linux machine, with the OS page cache and a local registry mirror warm in every scenario. Three samples per scenario show variation, not statistical certainty; where ranges overlap, the medians’ order can change between runs. Physical disk is measured before project checks; hardlinks count once and shared reflink extents are not identified. Upstream tarball URLs pinned in original lockfiles can bypass the mirror. A passing check means that command succeeded on the installed tree, not that the repository’s entire test suite passes.
The methodology explains isolation and disk accounting. Raw samples, package sets, commands and logs are in the repository’s benchmark results.