Skip to content

perf(pm): preload worker-pool replaces FuturesUnordered - #2869

Closed
elrrrrrrr wants to merge 2 commits into
nextfrom
perf/preload-worker-pool-only
Closed

elrrrrrrr wants to merge 2 commits into
nextfrom
perf/preload-worker-pool-only

Conversation

@elrrrrrrr

Copy link
Copy Markdown
Contributor

Summary

Single-commit re-attempt of PR #2827's worker-pool change, freshly cherry-picked on top of latest next (which now has aws-lc-rs #2856, OnceMap #2862, ManifestStore #2866 already merged).

Replaces the FuturesUnordered-based preload with N long-lived tokio::spawn workers pulling from a shared crossbeam_queue::SegQueue<Dep>. Each worker is independent on tokio's multi-thread runtime, so when one worker is parsing manifest JSON (CPU-bound, simd_json), other workers can still drive their network IO. Termination via dispatched/completed AtomicUsize + Notify.

Cherry-pick of 7e64c597 from PR #2818, with one resolution: RegistryClient::fetch_full_manifest now returns Arc<FullManifest> (from #2866) AND carries + MaybeSend (from this commit).

Wasm compat

Native uses real Send/Sync. Wasm32 uses MaybeSend/MaybeSync no-op marker traits because JsFuture is !Send and tasks run via wasm_bindgen_futures::spawn_local.

Local bench (mac, no proxy, ant-design cold-cold, 6 runs)

binary median
next (origin) 3.95s
worker-pool (this PR) 5.05s ⚠️
allwins (full PR #2818 stack) 3.05s
bun (npmmirror, --lockfile-only) 3.00s

⚠️ worker-pool alone regresses vs next on local mac (Cloudflare per-IP throttle dominates the per-request CPU savings the architecture buys). Matches PR #2838's driver-hunt finding: worker-pool needs the alloc-cleanup driver bundle to express its win. CI bench-phases on clean GH Actions egress is the authoritative signal — that's where PR #2818 measured −31% wall + 84 vs 55 avg_conc.

This PR is opened as draft specifically to get CI bench numbers.

Test plan

  • cargo build --release -p utoo-pm clean
  • cargo clippy -p utoo-ruborist -p utoo-pm --all-targets -- -D warnings --no-deps clean
  • cargo test -p utoo-ruborist 162 passed, cargo test -p utoo-pm 245 passed
  • Local cold smoke utoo deps on ant-design — exits 0, lockfile generated
  • CI pm-bench-phases-* (the real signal — both ubuntu + macos)
  • CI utooweb-ci-build-wasm (validates MaybeSend shim on wasm32)

🤖 Generated with Claude Code

@elrrrrrrr elrrrrrrr added bench-phases Run pm-bench-phases workflow benchmark Run pm-bench on PR and removed bench-phases Run pm-bench-phases workflow labels Apr 29, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the manifest preloading mechanism from a FuturesUnordered-based approach to a multi-threaded worker pool using a lock-free SegQueue and tokio::spawn. This change improves concurrency by allowing CPU-bound tasks like JSON parsing to run independently of network I/O. Additionally, a new MaybeSend/MaybeSync trait shim is introduced to support both native multi-threaded runtimes and wasm32 environments. I have identified an improvement opportunity regarding the calculation of processed items in the preload phase, where the current implementation is unnecessarily complex and inefficient compared to a direct call to DashSet::len().

Comment thread crates/ruborist/src/resolver/preload.rs Resolved
N long-lived `tokio::spawn` workers pull work from a shared
lock-free `SegQueue`. Each worker is independent on tokio's
multi-thread runtime, so when one worker is parsing manifest
JSON (CPU-bound, simd_json), other workers continue driving
their network IO and other parses run on different cores.
Replaces `FuturesUnordered` where the main task owned all
preload futures and polled them cooperatively — every per-
future await continuation (including parse) ran serialised
in that single task.

The architectural fix alone (proven nop in PR #2830 with
spawn_local) needs the network-layer drivers (aws-lc-rs +
OnceMap, earlier in this stack) to actually express its
benefit. With them: TLS handshake CPU is fast (aws-lc-rs),
duplicate fetches are deduped (OnceMap), so worker-pool's
N independent task slots are doing actual unique work.

`tokio::spawn` requires `Future: Send + 'static`, which
forces `RegistryClient` trait surface to grow `+ Send` on
its `impl Future` returns + `Self: Sync` / `Self::Error:
Send` on default impls. Native uses real Send/Sync; wasm32
uses no-op marker traits because `JsFuture` is `!Send` and
tasks run via `wasm_bindgen_futures::spawn_local`.

`PreloadRegistry` trait alias bundles `RegistryClient +
Clone + MaybeSend + MaybeSync + 'static` so the 5 resolver
entry points (`build_deps*`, `resolve*`, `run_preload_phase`)
read clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@elrrrrrrr
elrrrrrrr force-pushed the perf/preload-worker-pool-only branch from 6b5304f to cf926a3 Compare April 30, 2026 02:58
elrrrrrrr added a commit that referenced this pull request Apr 30, 2026
Bisect from #2870 (full bundle, regressed +17% wall on Linux p1_resolve).
Keeps worker-pool's resolver/builder/registry/traits/preload.rs
unchanged from #2869. Adds only the non-overlapping external
optimizations from PR #2818:

- pm/util/extractor.rs: rayon::spawn per package + par_chunks(32)
- pm/util/logger.rs: drop indicatif Mutex from preload hot path
- ruborist/resolver/semver.rs: normalize_spec returns Cow
- ruborist/resolver/workspace.rs: parallelise workspace package.json reads

Skips (deferred — were in #2870, suspect regression source):
- service/dns.rs (per-family DNS rotation)
- service/http.rs (TLS instrumentation overhead)
- service/manifest.rs
- resolver/preload.rs (post-7e64c597 evolution; tuned for #2818's
  full stack, not optimal in isolation per #2870 data)
- resolver/builder.rs / registry.rs / traits/registry.rs

Test plan:
- cargo build / clippy / cargo test -p utoo-ruborist (162 passed)
- 1 pre-existing flaky test (test_update_package_binary_fsevents)
  fails only when interleaved; passes in isolation. Unrelated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@elrrrrrrr elrrrrrrr added benchmark Run pm-bench on PR and removed benchmark Run pm-bench on PR labels Apr 30, 2026
The preload worker pool uses `wasm_bindgen_futures::spawn_local` on
wasm32 targets but the crate was missing from ruborist's dependencies.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

📊 pm-bench-phases · 5b71af5 · linux (ubuntu-latest)

Workflow run — ant-design

PMs: utoo (this branch) · utoo-npm (latest published) · bun (latest)

npmjs.org

p0_full_cold

PM wall ±σ user sys RSS pgMinor
bun 7.40s 0.04s 10.20s 6.10s 658M 283.8K
utoo-next 8.53s 0.45s 11.26s 8.62s 1.24G 162.3K
utoo-npm 9.18s 0.36s 11.19s 8.69s 1.25G 137.4K
utoo 9.34s 1.54s 12.29s 8.47s 1.47G 146.4K
PM vCtx iCtx netRX netTX cache node_mod lock
bun 17.5K 18.3K 1.17G 6M 1.83G 1.72G 1M
utoo-next 184.6K 161.3K 1.13G 4M 1.68G 1.68G 2M
utoo-npm 187.4K 163.7K 1.14G 5M 1.68G 1.68G 2M
utoo 155.6K 89.4K 1.13G 3M 1.68G 1.68G 2M

p1_resolve

PM wall ±σ user sys RSS pgMinor
bun 1.98s 0.10s 4.41s 0.80s 507M 171.0K
utoo-next 4.68s 0.02s 4.92s 1.43s 439M 76.5K
utoo-npm 4.77s 0.09s 4.78s 1.21s 431M 72.1K
utoo 2.73s 0.11s 6.21s 1.22s 561M 78.3K
PM vCtx iCtx netRX netTX cache node_mod lock
bun 8.4K 5.0K 202M 3M 104M - 1M
utoo-next 107.2K 6.2K 197M 2M 7M 3M 2M
utoo-npm 72.4K 2.1K 200M 2M 9M 5M 2M
utoo 50.1K 20.0K 196M 796K 7M 3M 2M

p3_cold_install

PM wall ±σ user sys RSS pgMinor
bun 6.01s 1.22s 5.80s 6.03s 614M 195.1K
utoo-next 8.23s 2.40s 5.65s 7.54s 939M 121.4K
utoo-npm 8.17s 2.68s 5.68s 7.48s 915M 102.4K
utoo 6.25s 1.97s 5.49s 7.03s 883M 118.3K
PM vCtx iCtx netRX netTX cache node_mod lock
bun 6.7K 6.1K 993M 4M 1.73G 1.73G 1M
utoo-next 148.9K 85.6K 965M 4M 1.67G 1.67G 2M
utoo-npm 150.7K 84.4K 965M 4M 1.67G 1.67G 2M
utoo 101.0K 58.6K 964M 2M 1.67G 1.67G 2M

p4_warm_link

PM wall ±σ user sys RSS pgMinor
bun 2.25s 0.12s 0.15s 1.23s 137M 32.0K
utoo-next 1.88s 0.06s 0.57s 2.32s 82M 19.4K
utoo-npm 1.99s 0.11s 0.57s 2.35s 82M 18.9K
utoo 1.85s 0.12s 0.57s 2.32s 82M 18.9K
PM vCtx iCtx netRX netTX cache node_mod lock
bun 404 13 7M 19K 1.88G 1.72G 1M
utoo-next 48.8K 18.0K 21K 11K 1.67G 1.67G 2M
utoo-npm 47.8K 17.1K 25K 18K 1.67G 1.67G 2M
utoo 49.3K 18.4K 26K 17K 1.68G 1.67G 2M

npmmirror.com: no output captured.

@elrrrrrrr

Copy link
Copy Markdown
Contributor Author

Superseded by #2874 (rayon-only architecture chosen over worker-pool refactor). Bench analysis showed worker-pool's p1 advantage was mostly from the network-layer commits (6afc0c84 h1+pool + 608db9f1 DNS rr), which #2874 cherry-picks directly. Worker-pool architecture itself contributed ~0.18s. Closing in favor of #2874.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

benchmark Run pm-bench on PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant