Zoltan Kochan d669c8bf17 perf(deps): sort dependency graphs in linear time (#14152)
graph_sequencer collected each topological chunk by rescanning every
included node, so a graph whose chains produce as many chunks as nodes
sorted in O(V * chunks) — with a full node hash (a PathBuf of the
project directory) paid on every probe. A 6,833-project workspace whose
projects each depend on their predecessors spent ~4 s per sort, and a
plain install sorts twice (the recursive project selection and the
workspace-cycle report), regressing pnpm:scope from 0.15 s to 4 s and
pnpm:package-manifest from 0.5 s to 4.3 s between v12.0.0-rc.8 (which
sorted nothing on a plain install) and rc.9 (where cfg.sort defaults the
recursive sort on and pnpm/pnpm#14074 added the cycle check).

Both implementations now intern the nodes to indices up front and gather
each chunk from the nodes whose degree the previous chunk's removal
dropped to zero, i.e. layered Kahn's in O(V log V + E) with no hashing
in the loop. Chunk membership and order (following `included`), the
cycle list, and the `safe` verdict are unchanged; the one divergence the
rewrite removes is that the Rust port used to emit a node twice into one
chunk when `included` listed it twice, where the TypeScript
implementation deduplicated — both now deduplicate.

The TypeScript graphSequencer had the same shape (its Map probes are
cheaper, so it hid the cost longer) and is rewritten identically to keep
the stacks aligned.

On the pnpm/pnpm#14151 reproduction (6,833 projects), a warm no-op
`pnpm install --lockfile-only` drops from 9.0 s (rc.9) to 1.7 s, and a
one-dependency lockfile update from 14.2 s to 5.9 s — faster than
Yarn 4's 7.2 s on the same change.

The cycle pass is confined to strongly connected components (iterative
Tarjan): nodes that merely lead into a cycle no longer pay a
reachability walk each, and one ring's search cannot walk downstream
rings. Reported cycles, chunk order, and the safe verdict are
unchanged. The TypeScript side also appends cycle ids without a
call-spread (argument-limit overflow on huge cycles) and replaces
queue.shift() with a head index.

A new workspace_sort micro-benchmark group covers the shape every
existing benchmark missed (single-project integrated fixture; the
workspace_resolution group starts downstream of sorting): the projects
graph build plus sequencing over 4,000 chained projects — 538 ms
quadratic, 15.7 ms now.

Closes pnpm/pnpm#14149.
Closes pnpm/pnpm#14151.
2026-08-25 13:51:56 +02:00
…
…
…
…
…
…
…

简体中文 | 日本語 | 한국어 | Italiano | Português Brasileiro

pnpm

Fast, disk space efficient package manager:

  • Fast. Up to 2x faster than the alternatives (see benchmark).
  • Efficient. Files inside node_modules are linked from a single content-addressable storage.
  • Great for monorepos.
  • Strict. A package can access only dependencies that are specified in its package.json.
  • Deterministic. Has a lockfile called pnpm-lock.yaml.
  • Works as a Node.js version manager. See pnpm runtime.
  • Works everywhere. Supports Windows, Linux, and macOS.
  • Battle-tested. Used in production by teams of all sizes since 2016.
  • Experimental Rust port. Includes pacquet, an experimental port of the CLI written in Rust.
  • See the full feature comparison with npm and Yarn.

To quote the Rush team:

Microsoft uses pnpm in Rush repos with hundreds of projects and hundreds of PRs per day, and we’ve found it to be very fast and reliable.

npm version Ecosystem E2E OpenCollective OpenCollective X Follow Stand With Ukraine

Platinum Sponsors

Bit OpenAI Notion

Gold Sponsors

Sanity Discord Vite
SerpApi CodeRabbit Stackblitz
Workleap Nx Latitude

Silver Sponsors

Replit Cybozu BairesDev
Thesys devowl.io u|screen
Leniolabs_ Depot Cerbos
⏱️ Time.now

Support this project by becoming a sponsor.

Background

pnpm uses a content-addressable filesystem to store all files from all module directories on a disk. When using npm, if you have 100 projects using lodash, you will have 100 copies of lodash on disk. With pnpm, lodash will be stored in a content-addressable storage, so:

  1. If you depend on different versions of lodash, only the files that differ are added to the store. If lodash has 100 files, and a new version has a change only in one of those files, pnpm update will only add 1 new file to the storage.
  2. All the files are saved in a single place on the disk. When packages are installed, their files are linked from that single place consuming no additional disk space. Linking is performed using either hard-links or reflinks (copy-on-write).

As a result, you save gigabytes of space on your disk and you have a lot faster installations! If you'd like more details about the unique node_modules structure that pnpm creates and why it works fine with the Node.js ecosystem, read this small article: Flat node_modules is not the only way.

💖 Like this project? Let people know with a tweet

Getting Started

Benchmark

pnpm is up to 2x faster than npm and Yarn classic. See all benchmarks here.

Benchmarks on an app with lots of dependencies:

License

MIT, except the pnpr/ directory, which is source-available under the PolyForm Shield License 1.0.0.

S
Description
No description provided
Readme MIT
405 MiB
0 Stars 1 Watchers 0 Forks
Languages
Rust 68.8%
TypeScript 29.4%
JavaScript 1.6%
Shell 0.1%