Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf(npm): optimize loading npm resolution snapshot from lockfile by only loading necessary version info #27261

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

nathanwhit
Copy link
Member

@nathanwhit nathanwhit commented Dec 6, 2024

This is sort of a POC, the actual code is sus but works well enough to see the perf impact on real world projects.

Basically when we're loading the npm resolution snapshot from a lockfile, we read (and deserialize!) a bunch of registry.json files from the cache dir.

The thing is, those registry.json files can be pretty massive (e.g. nextJS's registry.json is ~6MB), and reading and deserializing them is very expensive.

We don't really need the entire NpmPackageInfo though, we just want the information for a specific version (the one in the lockfile).

This PR optimizes this by creating an index of the registry.json files that gives the file offsets containing the version info for each version. Then, when we load the snapshot we read the index to get the byte offsets for the version we need, and only load (and deserialize) that part of the registry.json file.


Results (this is in a project from create-next-app)

Running an empty file with --node-modules-dir=auto (which eagerly loads the resolution snapshot):

❯ hyperfine --warmup 20 'deno run --node-modules-dir=auto -A empty.js' '/Users/nathanwhit/Documents/Code/deno/target/release/deno run --node-modules-dir=auto -A empty.js'
Benchmark 1: deno run --node-modules-dir=auto -A empty.js
  Time (mean ± σ):     213.8 ms ±   2.7 ms    [User: 197.2 ms, System: 17.0 ms]
  Range (min … max):   210.0 ms … 218.4 ms    13 runs

Benchmark 2: /Users/nathanwhit/Documents/Code/deno/target/release/deno run --node-modules-dir=auto -A empty.js
  Time (mean ± σ):      19.8 ms ±   0.6 ms    [User: 13.2 ms, System: 7.8 ms]
  Range (min … max):    18.8 ms …  21.9 ms    138 runs

Summary
  /Users/nathanwhit/Documents/Code/deno/target/release/deno run --node-modules-dir=auto -A empty.js ran
   10.82 ± 0.33 times faster than deno run --node-modules-dir=auto -A empty.js

Deno install with a populated cache dir, but setting up node modules from scratch:

❯ hyperfine --warmup 2 --shell=none --prepare "rm -rf node_modules" "deno i" "/Users/nathanwhit/Documents/Code/deno/target/release/deno i"
Benchmark 1: deno i
  Time (mean ± σ):     395.5 ms ±   6.6 ms    [User: 194.7 ms, System: 200.8 ms]
  Range (min … max):   388.0 ms … 409.8 ms    10 runs

Benchmark 2: /Users/nathanwhit/Documents/Code/deno/target/release/deno i
  Time (mean ± σ):     187.1 ms ±  13.5 ms    [User: 10.0 ms, System: 196.6 ms]
  Range (min … max):   167.9 ms … 211.6 ms    10 runs

Summary
  /Users/nathanwhit/Documents/Code/deno/target/release/deno i ran
    2.11 ± 0.16 times faster than deno i

A no-op deno install (node_modules already set up):

❯ hyperfine --warmup 20 --shell=none "deno i" "/Users/nathanwhit/Documents/Code/deno/target/release/deno i"
Benchmark 1: deno i
  Time (mean ± σ):     212.2 ms ±   4.8 ms    [User: 194.8 ms, System: 15.9 ms]
  Range (min … max):   206.5 ms … 225.2 ms    14 runs

Benchmark 2: /Users/nathanwhit/Documents/Code/deno/target/release/deno i
  Time (mean ± σ):      11.5 ms ±   0.3 ms    [User: 6.2 ms, System: 5.0 ms]
  Range (min … max):    10.8 ms …  12.5 ms    255 runs

Summary
  /Users/nathanwhit/Documents/Code/deno/target/release/deno i ran
   18.53 ± 0.67 times faster than deno i

Copy link
Member

@lucacasonato lucacasonato left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, this is super great

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants