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 92 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

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.

7 participants

Comments