perf(npm): index packuments with SIMD JSON scanner#35698
Conversation
bartlomieju
left a comment
There was a problem hiding this comment.
Strong PR — the lazy-index design is sound and the correctness-critical invariants hold up under inspection. I traced the slim-cache field coverage, the trust-evidence round-trip, time handling, and the indexed-vs-fallback trust path, and found no blocking bugs. My comments are about robustness/documentation of two load-bearing tricks plus CI coverage; overall this is approve-pending-green-CI.
Things I checked that are fine: cross-block strings / escaped quotes / split UTF-8 / unclosed input are covered by tests + fuzz + a serde_json differential over 64 generated packuments; the slim dist allowlist matches NpmPackageVersionDistInfo exactly; exports is skip_deserializing and the LSP fetches the full packument separately (cli/npm.rs), so dropping it from the slim cache is safe; the indexed trust fast-path in common.rs is semantically identical to the fallback loop; and the lazy variant's mutating methods aren't on the resolution hot path.
Inline comments cover: (1) x86_64/Windows CI coverage, (2) the slim allowlist as a silent-data-loss trap for future fields, (3) the synthetic provenance:true, (4) the etag byte-scan invariant.
Nits (non-blocking): once_cell::sync::OnceCell could be std::sync::OnceLock; keys()/values()/iter() return Box<dyn Iterator> (a heap alloc per call on a per-package path — an enum iterator would avoid it); dev-dependency cycle deno_npm <-> fast-registry-json; confirm the incidental windows-sys/libloading bumps in Cargo.lock are pulled by the new deps and not a stray cargo update; pluck_versions is now only used by benches/fuzz.
| output.push(b'{'); | ||
| let mut first = true; | ||
| let mut wrote_version = false; | ||
| for key in [ |
There was a problem hiding this comment.
This hardcoded allowlist is now the only thing that determines which version fields survive into the persisted cache. I verified it currently covers every field NpmPackageVersionInfo deserializes except scripts (intentional -> hasInstallScript) and exports (skip_deserializing, LSP-only). The risk is the next field added to NpmPackageVersionInfo: it'll be silently dropped from cache with no compile error, and the stale data is sticky. This is strictly worse than the old slim_full_packument, which kept everything-except-scripts. Suggest a round-trip test (fully-populated NpmPackageVersionInfo -> slim_package_info_bytes -> from_packument_slice, asserting field-for-field equality) plus a comment on the struct pointing back here.
8671d49 to
2f7087c
Compare
2f7087c to
5aff3a3
Compare
a54c4ed to
9f67594
Compare
Summary
This adds
libs/fast-registry-json, a specialized npm packument indexer, and uses it when loading npm package info. The intent is to avoid eagerly deserializing every version manifest in large packuments when resolution usually only needs a small subset of them.The new parser is not a general replacement for
serde_json. It is a narrow indexer for registry JSON:versions[version]manifestserde_jsonSIMD Scanner
fast-registry-jsonis adapted from simdjson-style stage 1 scanning, but narrowed for npm registry packuments rather than general-purpose JSON parsing.At a high level, it scans the input in 64-byte blocks, identifies string ranges and structural JSON characters, then feeds those tokens into a small packument-specific state machine. The state machine records only the data Deno needs for npm resolution:
name,dist-tags,versionskeys and byte ranges,time,_deno.etag, and compact trust evidence.Target behavior:
OnceLock; SSSE3 gets the table-shuffle classifier, otherwise it falls back to comparison-based classificationmovemask, which is baseline for x86_64wide/comparison pathUnsupported SIMD targets still have a fallback path rather than requiring the fastest classifier.
Deno Integration
NpmPackageInfo::from_packument_slice()now uses the packument index directly. It parses the cheap top-level data eagerly:minimumDependencyAgeprovenance,trustedPublisher, staged publish approver)The
versionsmap is now represented byNpmPackageVersionInfos, which can be either materialized or lazy. The lazy variant stores:Arc<str>Version -> Range<usize>mapOnceCell<Option<NpmPackageVersionInfo>>Resolution can iterate version keys, check publish times, apply trust policy, and then deserialize only the selected manifest. Existing paths that need all manifests can still iterate values or mutate the map; those paths materialize as needed.
Cache Changes
The npm cache path now writes slim packument bytes from the indexed source rather than deserializing the full packument and serializing it again.
The slim cache keeps the resolution-relevant fields:
dist-tagsversionstimehasInstallScript_deno.etagThis keeps
minimumDependencyAgeworking for full packuments and avoids defeating laziness when saving the cache.Benchmarks
Built with
cargo build -p deno --profile release-lite.Compared:
/Users/nathanwhit/.deno/bin/denotarget/release-lite/denoCommand shape:
For each measured run,
node_modulesanddeno.lockwere removed. Each binary used a separate warmedDENO_DIR.Targeted Fixtures
drizzle-ormonlyWith lockfiles preserved, both fixtures were effectively neutral because Deno does not need to read packuments on that path.
../vlt-benchmarksFixturesWarm-cache, no-lockfile, run in place in
../vlt-benchmarks/fixtures/*:astronextsveltevuerunlargebabylonThe
largefixture is wall-time neutral because filesystem /node_moduleswork dominates, though the current branch used much less user CPU. The common app fixtures are consistently about 1.3-1.4x faster.x86_64 Linux
../vlt-benchmarksFixturesHost:
[email protected], x86_64 Broadwell, canary upgraded withdeno upgrade canary.Compared:
deno 2.9.1+4e8b2f4 (canary, release, x86_64-unknown-linux-gnu)deno 2.9.1 (stable, release, x86_64-unknown-linux-gnu)Command shape:
Each binary used a separate warmed
DENO_DIRpopulated by that binary. For each measured run,node_modulesanddeno.lockwere removed, preserving normalnode_modulesbehavior.appfixtures/astrofixtures/babylonfixtures/largefixtures/nextfixtures/runfixtures/sveltefixtures/vueFull log:
/home/bot/work/vlt-deno-install-all-fixtures-20260702-200452.logon the x64 benchmark host.x86_64 Packument Index Classifier Check
Raw full registry packuments were downloaded directly from
https://registry.npmjs.org/<pkg>and benchmarked withcargo bench -p fast-registry-json --bench benchmark -- bench_packument_index_. The optimized x86 path uses SSSE3 runtime dispatch; the fallback measurement temporarily forced the comparison classifier.client-onlynode-ptydrizzle-kitnextdrizzle-orm@prisma/clientCaveats
serde_json.Validation
cargo test -p fast-registry-json -p deno_npm -p deno_npm_cachecargo clippy -p fast-registry-json -p deno_npm -p deno_npm_cache --all-targets -- -D warningscargo test -p specs_tests --test specs -- specs::install::minimum_dependency_age_slim_packumentcargo test -p specs_tests --test specs -- specs::npm::trust_signalscargo fmt --checkcargo bench -p fast-registry-json --bench benchmark --no-run