Skip to content

stream: drain WHATWG stream queues from a head index#64354

Open
mcollina wants to merge 2 commits into
nodejs:mainfrom
mcollina:webstream-perf-round7
Open

stream: drain WHATWG stream queues from a head index#64354
mcollina wants to merge 2 commits into
nodejs:mainfrom
mcollina:webstream-perf-round7

Conversation

@mcollina

@mcollina mcollina commented Jul 8, 2026

Copy link
Copy Markdown
Member

Drain the default readable and writable controller queues through a moving head index instead of ArrayPrototypeShift (which is O(queue length), making a buffered drain O(n^2)). Each dequeue becomes O(1); the consumed prefix is dropped in amortized O(1).

readable-read-buffered: +14.8% to +35.4% (**/***)
readable-async-iterator type=normal: +17.8% (***)
pipe-to: +12.9% to +18.1% (all 16 configs ***)

The first commit is #64320; only the last commit is new here.

mcollina added 2 commits July 7, 2026 19:52
Skip the size algorithm call and its result validation when the size
algorithm is the default one, and stop re-running the full
ShouldCallPull predicate at per-chunk call sites where its inputs are
already established.

readable-async-iterator type=normal: +16.6% (***)
readable-read-buffered: +13.9% to +27.5% (**/***)
pipe-to: +3.7% to +6.0% (15/16 configs significant)

Signed-off-by: Matteo Collina <hello@matteocollina.com>
The default readable and writable controller queues were drained with
ArrayPrototypeShift, which is O(queue length); reading a buffered queue one
chunk at a time is therefore O(n^2). Dequeue through a moving head index
instead so each read is O(1), dropping the consumed prefix in amortized
O(1) once it reaches half of the backing array (past a small floor, so a
short oscillating queue never pays a per-dequeue array mutation).

readable-read-buffered: +14.8% to +35.4% (**/***)
readable-async-iterator type=normal: +17.8% (***)
pipe-to: +12.9% to +18.1% (all 16 configs ***)

Signed-off-by: Matteo Collina <hello@matteocollina.com>
@nodejs-github-bot nodejs-github-bot added needs-ci PRs that need a full CI run. web streams labels Jul 8, 2026
@mcollina mcollina requested a review from MattiasBuelens July 8, 2026 11:37
@codecov

codecov Bot commented Jul 8, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.23%. Comparing base (f4c85d5) to head (8b43ce8).
⚠️ Report is 7 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #64354      +/-   ##
==========================================
- Coverage   90.24%   90.23%   -0.01%     
==========================================
  Files         741      741              
  Lines      240976   241059      +83     
  Branches    45408    45419      +11     
==========================================
+ Hits       217477   217530      +53     
- Misses      15070    15099      +29     
- Partials     8429     8430       +1     
Files with missing lines Coverage Δ
lib/internal/webstreams/readablestream.js 98.18% <100.00%> (+0.02%) ⬆️
lib/internal/webstreams/util.js 99.68% <100.00%> (+0.01%) ⬆️
lib/internal/webstreams/writablestream.js 99.50% <100.00%> (+<0.01%) ⬆️

... and 34 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

// each dequeue O(1); the consumed prefix is dropped in amortized O(1) once
// it grows to at least half of the backing array (and past a small floor,
// so a short oscillating queue never pays a per-dequeue array mutation).
// Callers must treat `queue.length - queueHead`, not `queue.length`, as the

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.

Perhaps we should have a helper function for that?

function queueLength(controller) {
  const state = controller[kState];
  return state.queue.length - state.queueHead;
}

Or does that not get inlined as aggressively as we'd want?

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

Labels

needs-ci PRs that need a full CI run. web streams

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants