next.js
f58c038c - turbo-tasks-backend: parallelize decoding of large batch lookups (#100453)

Commit
17 days ago
turbo-tasks-backend: parallelize decoding of large batch lookups (#100453) ### What? `TurboBackingStorage::batch_lookup_data` now decodes the `TaskStorage` entries of a batch in parallel when the batch has at least `PARALLEL_DECODE_THRESHOLD` (2048) items. Smaller batches still decode sequentially. This PR also adds an opt-in Criterion benchmark that measures where parallel decoding starts to pay off. ### Why? Batched task restores read all entries with a single `batch_get` and then decoded every entry one after another on the calling thread. With large restore batches, that single-threaded decode is a bottleneck. ### How? - `batch_lookup_data` is now split into two steps: - a raw fetch (`batch_lookup_raw`), unchanged I/O; - `decode_task_storage_batch`, which takes the parallel cutoff as a parameter. - Both paths share the same decode closure, so ordering, `None` for missing keys, and failing the batch on a decode error behave exactly as before. - The parallel path uses `turbo_tasks::parallel::map_collect`. It borrows the raw bytes, keeps result order and the turbo-tasks context, and doesn't add another thread pool. - `turbo_tasks::scope_bounded` (behind `parallel::*`) now enters the turbo-tasks context once per helper instead of once per job: - #100485 fixed re-entering the context while the caller holds a borrow (restores can run inside `with_turbo_tasks`). Its fix is `ensure_turbo_tasks_scope`, which compares the current task-local on every job. - Now, matching `scope_unbounded`, each helper task enters the captured context once around its drain loop. Jobs drained inline by the calling thread run directly in the context that is already there. This drops the per-job `Arc` clone and task-local check. - `ensure_turbo_tasks_scope` is removed, since nothing else uses it. - The benchmark internals are exposed through a `#[doc(hidden)] pub mod private` in `turbo-tasks-backend`. No public method is added to `TurboBackingStorage`. - New benchmark `benches/batch_lookup.rs`, gated behind an env var like the GC bench: - Setup builds and persists a real task graph, then reads the raw Meta/Data bytes once. - It times only the decode call, on a Tokio worker thread, for 2–8192 items. - It compares sequential, parallel, and the default threshold. ```bash TURBOPACK_BENCH_BATCH_LOOKUP=1 cargo bench -p turbo-tasks-backend --bench mod -- batch_lookup ``` Results on an 8 vCPU machine (decode time, µs): | Items | Meta seq | Meta par | Data seq | Data par | |---:|---:|---:|---:|---:| | 64 | 7.8 | 25.4 | 10.8 | 29.3 | | 512 | 62.2 | 81.6 | 78.9 | 73.4 | | 1024 | 123.9 | 136.4 | 159.2 | 118.5 | | 2048 | 255.5 | 180.6 | 338.0 | 220.8 | | 4096 | 524.2 | 331.9 | 711.1 | 401.0 | | 8192 | 1169.1 | 626.9 | 1410.7 | 728.6 | - Parallel decoding is 2–8× slower below roughly 256 items. - Data breaks even around 512–1024 items, while Meta is still slower at 1024. - At 2048 both win (1.42× for Meta, 1.53× for Data), so the shared threshold is set conservatively at 2048. These numbers come from a synthetic graph, so the crossover will vary with core count, task contents, and how busy the runtime is. They measure decode time only, not end-to-end build time. ### Tests - New unit test: sequential and forced-parallel decoding produce identical results for Meta and Data, including missing entries and empty input. Both modes fail on malformed bytes. - New round-trip test: calls `batch_lookup_data` against a real database at 2047 and 2048 items, with reversed order, duplicate ids, and missing ids. - The existing regression test `parallel_inside_with_turbo_tasks` (from #100485, in `turbo-tasks-backend/tests/invalidate_all.rs`) passes with the per-helper context. So do the 7 `node_file_trace_persistent` cases that panicked when large restore batches first started decoding in parallel. - Ran `cargo test -p turbo-tasks-backend --lib --tests`, `cargo test -p turbo-tasks --lib`, the WASI lib tests (`--target wasm32-wasip1-threads` with the Node WASI host), rustdoc with warnings denied, `cargo clippy -p turbo-tasks -p turbo-tasks-backend --tests --benches -- -D warnings`, and `cargo fmt --check`. <!-- NEXT_JS_LLM --> <!-- fleet 0b5d5738-9eb7-40fb-953e-c8e74f8635dc --> --------- Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
Author
Parents
Loading