Skip to content

stream: add fast paths for webstreams read and pipeTo#61807

Open
mcollina wants to merge 1 commit intonodejs:mainfrom
mcollina:webstreams-fast-paths
Open

stream: add fast paths for webstreams read and pipeTo#61807
mcollina wants to merge 1 commit intonodejs:mainfrom
mcollina:webstreams-fast-paths

Conversation

@mcollina
Copy link
Member

@mcollina mcollina commented Feb 13, 2026

Add internal fast paths to improve webstreams performance without changing the public API or breaking spec compliance.

  1. ReadableStreamDefaultReader.read() fast path: When data is already buffered in the controller's queue, return PromiseResolve() directly without creating a DefaultReadRequest object. This is spec-compliant because read() returns a Promise, and resolved promises still run callbacks in the microtask queue.

  2. pipeTo() batch read fast path: When data is buffered, batch reads directly from the controller queue up to highWaterMark without creating PipeToReadableStreamReadRequest objects per chunk. Respects backpressure by checking desiredSize after each write.

Benchmark results:

  • pipeTo: ~11% faster (***)
  • buffered read(): ~17-20% faster (***)

This was done in partnership with Vercel to improve the performance of React and Next.js, and from a conversation on X with @cramforce.

@nodejs-github-bot
Copy link
Collaborator

Review requested:

  • @nodejs/performance
  • @nodejs/web-standards

@nodejs-github-bot nodejs-github-bot added needs-ci PRs that need a full CI run. web streams labels Feb 13, 2026
@codecov
Copy link

codecov bot commented Feb 13, 2026

Codecov Report

❌ Patch coverage is 98.71795% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 89.71%. Comparing base (ae2ffce) to head (76ccbaf).
⚠️ Report is 98 commits behind head on main.

Files with missing lines Patch % Lines
lib/internal/webstreams/readablestream.js 98.71% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #61807      +/-   ##
==========================================
- Coverage   89.75%   89.71%   -0.05%     
==========================================
  Files         674      675       +1     
  Lines      204416   204884     +468     
  Branches    39285    39377      +92     
==========================================
+ Hits       183472   183807     +335     
- Misses      13227    13338     +111     
- Partials     7717     7739      +22     
Files with missing lines Coverage Δ
lib/internal/webstreams/readablestream.js 98.45% <98.71%> (+<0.01%) ⬆️

... and 70 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.

@mcollina mcollina added the request-ci Add this label to start a Jenkins CI on a PR. label Feb 13, 2026
@github-actions github-actions bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Feb 13, 2026
@mcollina mcollina force-pushed the webstreams-fast-paths branch from 1ad0edd to 080e458 Compare February 13, 2026 20:36
@nodejs-github-bot
Copy link
Collaborator

@mcollina mcollina force-pushed the webstreams-fast-paths branch from 080e458 to 5e0474e Compare February 13, 2026 20:37
@mcollina
Copy link
Member Author

@jasnell @Qard Can I get another approval?

Copy link
Contributor

Choose a reason for hiding this comment

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

Comparing batchCount to hwm is only valid if controller[kState].sizeAlgorithm is the default size algorithm (i.e. each chunk has size 1). I don't think we can assume that?

We should add a test that uses a custom size algorithm.

Copy link
Contributor

Choose a reason for hiding this comment

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

Why || 1? An HWM of 0 is valid.

Add internal fast paths to improve webstreams performance without
changing the public API or breaking spec compliance.

1. ReadableStreamDefaultReader.read() fast path:
   When data is already buffered in the controller's queue, return
   PromiseResolve() directly without creating a DefaultReadRequest
   object. This is spec-compliant because read() returns a Promise,
   and resolved promises still run callbacks in the microtask queue.

2. pipeTo() batch read fast path:
   When data is buffered, batch reads directly from the controller
   queue up to highWaterMark without creating
   PipeToReadableStreamReadRequest objects per chunk. Respects
   backpressure by checking desiredSize after each write.

Benchmark results:
  - pipeTo:          ~11% faster (***)
  - buffered read(): ~17-20% faster (***)

Co-Authored-By: Malte Ubl <malte@vercel.com>
@mcollina mcollina force-pushed the webstreams-fast-paths branch from 5e0474e to 76ccbaf Compare February 18, 2026 17:34
Copy link
Member

@gurgunday gurgunday left a comment

Choose a reason for hiding this comment

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

lgtm

Copy link
Member

@mertcanaltin mertcanaltin left a comment

Choose a reason for hiding this comment

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

LGTM

@mcollina mcollina added the request-ci Add this label to start a Jenkins CI on a PR. label Feb 19, 2026
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. request-ci Add this label to start a Jenkins CI on a PR. web streams

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

Comments