Passer au contenu principal

Benchmarks of JavaScript Package Managers

Last benchmarked at: Aug 14, 2026, 8:47 AM (daily updated).

This benchmark compares the performance of npm, pnpm, Yarn, Yarn PnP, and Bun (check Yarn's benchmarks for any other Yarn modes that are not included here). Every package manager installs through the same pnpr registry (v0.1.0-alpha.6) across an emulated 50ms round trip at 200 Mbit/s, so they all face one registry over one reproducible network instead of whatever link the benchmark machine happens to have. pnpm is measured twice: once on its own, and once resolving its dependency graph on the server instead of walking it itself. The page also compares how fast pnpm, fnm, and nvm install and switch Node.js versions.

About the setup:

  • Every manager crosses the same link. The round trip is applied to all of them, and to pnpm's resolution requests as well, so no client gets a cheaper connection than another.
  • pnpr's cache is warmed before anything is timed, so no manager pays to pull the fixture into the registry on behalf of the ones measured after it.
  • Server-side resolution pays off when there is a graph to resolve. Resolving one means walking it level by level, and each level costs a round trip, so the cost is roughly the depth of the graph times the latency. pnpr does that walk next to the registry and answers with the whole resolved lockfile at once, which is why the rows without a lockfile — and the row that changes dependencies — are the ones where it pulls ahead of plain pnpm.
  • It costs a round trip when there is not. pnpm asks the server on every install, including the ones where the lockfile is already up to date and there is nothing to work out. The server answers a question it has been asked before from its cache, in a few milliseconds — what the client pays for is the round trip and having the resolved lockfile streamed back, which plain pnpm never pays because it never asks. That is why the rows with a lockfile come out slightly behind plain pnpm instead of level with it.
  • Tarballs are still fetched by the client, in parallel and directly, on every row.

Each row's label lists which of cache, lockfile, and node_modules are warm/present before install runs. Quick mapping to the real world (ordered from slowest to fastest scenario):

  • clean: a brand-new clone — nothing cached, no lockfile, no node_modules.
  • cache: a developer reinstalling without a lockfile.
  • lockfile: a CI server doing its first install.
  • cache+lockfile: a developer reinstalling a known project.
  • cache+node_modules: the lockfile is deleted and install is run again.
  • node_modules: the cache and lockfile are deleted and install is run again.
  • cache+lockfile+node_modules: re-running install when nothing has changed.
  • lockfile+node_modules: the cache is deleted and install is run again.
  • update: dependency versions are bumped in package.json and install is run again.

Lots of Files

The app's package.json here

actioncachelockfilenode_modulesnpmpnpmpnpm 🦀pnpm + pnprYarnYarn PnPBun
install58.4s8s2s5s7s3.6s4.1s
install55.4s4.6s1.2s2.7s5.5s3.6s767ms
install13.4s7s2.2s5.2s4.4s1.1s1.7s
install10.4s2.4s711ms2.7s2s142ms743ms
install1.9s580ms58ms602ms3.7sn/a1.9s
install1.9s594ms65ms604ms7.4sn/a3.4s
install1.4s517ms18ms456ms1.3sn/a45ms
install1.4s533ms73ms1.7s4.9sn/a46ms
updaten/an/an/a10s8.3s1.2s2.9s2.6s2.5s584ms
Graph of the alotta-files results

pnpm vs pnpm 🦀

pnpm v12 will use a new installation engine for fetching and linking written in Rust. See pacquet.

actioncachelockfilenode_modulespnpmpnpm 🦀
install8s2s
install7s2.2s
install4.6s1.2s
install2.4s711ms
install594ms65ms
install580ms58ms
install533ms73ms
install517ms18ms
updaten/an/an/a8.3s1.2s
Graph comparing pnpm versions on the alotta-files fixture

Node.js Version Management

pnpm installs and switches Node.js versions itself, so a separate version manager is not needed. This section compares pnpm runtime set node with fnm and nvm.

scenariopnpm 12fnmnvm
install Node.js 24 with nothing cached1s2.3s3s
install Node.js 24 that was installed before189ms2.4s2.8s
run node in a project pinned to Node.js 228ms5ms93ms
Graph comparing Node.js version managers on installing Node.js

A few things to keep in mind when reading these numbers:

  • pnpm keeps Node.js in its content-addressable store and nvm keeps the downloaded tarballs in $NVM_DIR/.cache, so for both of them installing a version that was installed before needs no download. fnm has no download cache and fetches Node.js again.
  • pnpm doesn't extract the npm, npx, and corepack binaries bundled with Node.js, so on a clean install it downloads and writes fewer files than the other two.
  • Per-project switching costs no command at all in pnpm: the node on your PATH is a shim that reads the devEngines.runtime of the project and runs the matching version. fnm and nvm read a .node-version or .nvmrc file through a shell hook that fires on cd, which is what fnm exec and nvm use measure in that row. Loading nvm into the shell in the first place is not counted at all here, and it costs more than everything in that row.
  • All three have to materialize the pinned version the first time a project asks for it: pnpm links it from its store, fnm downloads it, nvm unpacks it. The row measures the repeated runs after that.