Implementation of Readable iteration helpers that does not rely on Async Iteration - #64429

Open
lukiano wants to merge 6 commits into
nodejs:mainfrom
lukiano:chore/faster-map-2
Open

Implementation of Readable iteration helpers that does not rely on Async Iteration#64429
lukiano wants to merge 6 commits into
nodejs:mainfrom
lukiano:chore/faster-map-2

Conversation

@lukiano

@lukianolukiano commented Jul 11, 2026

Copy link
Copy Markdown

Hi, I'd like to offer an alternative implementation of Readable iteration helpers like find() that don't rely on for await (...) to consume items from the stream. This results in improved performance when the data is already available, such as when creating a Readable from an array, as shown in the attached screenshot (executed on a MacBook M2 Max).

The main con is increased duplication and code complexity. My understanding is that these helpers are still in the experimental phase, which I hope makes a change like this easier to accept.

There are many commits in the branch, but I'll squash them before merging.
Edit: I merged them to fix CI issues

The affected helpers are

  • map()
  • filter()
  • reduce()
  • find()
  • toArray()
  • some()
  • every()
  • drop()

Unaffected helpers:

  • flatmap()
  • take()

There's also a change to the from() method that increases the buffer watermark when the source is an array of data.
At first, I coded the changes manually, but eventually I had assistance from AI to keep backward compatibility. Still, there's a small break as can be seen in the updated test in test/parallel/test-stream-reduce.js.

The file benchmark/streams/operator-throughput.js allows interested parties to run the benchmarks on their computers or modify them to try other scenarios. I can remove it before merging.

stream-operator-throughput-summary

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/performance
  • @nodejs/streams

@nodejs-github-botnodejs-github-bot added needs-ci PRs that need a full CI run. stream Issues and PRs related to Node.js streams. labels Jul 11, 2026
@codecov

codecovBot commented Jul 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.99065% with 45 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.15%. Comparing base (9ad097b) to head (df55497).
⚠️ Report is 249 commits behind head on main.

Files with missing linesPatch %Lines
lib/internal/streams/operators.js92.90%43 Missing and 2 partials ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #64429 +/- ##
==========================================
- Coverage 90.33% 90.15% -0.19% 
==========================================
Files 751 751 Lines 250341 254051 +3710 Branches 47322 47897 +575 ==========================================
+ Hits 226145 229037 +2892 - Misses 15575 16276 +701 - Partials 8621 8738 +117 
Files with missing linesCoverage Δ
lib/internal/streams/from.js91.55% <100.00%> (+4.79%)⬆️
lib/internal/streams/operators.js94.11% <92.90%> (-2.04%)⬇️

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

@lukiano
lukianoforce-pushed the chore/faster-map-2 branch from fae33f0 to 17e0105CompareJuly 12, 2026 18:24
@lukiano

Copy link
Copy Markdown
Author

I'm seeing #64447, which may change the measured performance of the current implementation.

@lukiano
lukianoforce-pushed the chore/faster-map-2 branch from 17e0105 to 43335b2CompareJuly 15, 2026 05:49
@lukiano
lukianoforce-pushed the chore/faster-map-2 branch 3 times, most recently from e10c1f8 to 9050881CompareAugust 2, 2026 22:49
@lukiano

Copy link
Copy Markdown
Author

macbook-stream-operator-throughput-report.pdf
wsl2-stream-operator-throughput-report.pdf
linux-server-stream-operator-throughput-report.pdf
I've run the performance comparisons again with the latest changes from the main branch. macbook is the same M2 Max MacBook as before, linux-server is a 10900KF Intel CPU running on Debian 13, and wsl2 is a Windows machine with an AMD 9900x processor, and the tests ran within a WSL2 with Ubuntu 24.04

@lukiano
lukianoforce-pushed the chore/faster-map-2 branch 4 times, most recently from b3d857e to df55497CompareAugust 24, 2026 17:01
@lukiano

Copy link
Copy Markdown
Author

@nodejs/streams I know you guys have probably 20 million PRs to review but if you could add this one to the queue I'd really appreciate it :)

@aduh95aduh95 added the large-pr PRs subject to the large-PR policy. label Aug 28, 2026
@ovflowdovflowd added the request-ci Add this label to start a Jenkins CI on a PR. label Aug 29, 2026

@ovflowdovflowd left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The changes on streams/operators are quite significant, making the review of the code much harder. It'd be helpful if you could reorganize your changes in a way that minimizes diff. Or decouple current behavior from the added behavior of "data already being available"

My main concern is due to the nature of the diff, it is hard to see/visualize what actually was changed in terms of behavior/logic versus what is just moving code around. I'm not an expert on the streams implementation within node, but possibly my peers would argue the same.

Do you believe there any improvements you can do to the current diff?

@lukiano

lukiano commented Aug 29, 2026

Copy link
Copy Markdown
Author

The changes on streams/operators are quite significant, making the review of the code much harder. It'd be helpful if you could reorganize your changes in a way that minimizes diff. Or decouple current behavior from the added behavior of "data already being available"

My main concern is due to the nature of the diff, it is hard to see/visualize what actually was changed in terms of behavior/logic versus what is just moving code around. I'm not an expert on the streams implementation within node, but possibly my peers would argue the same.

Do you believe there any improvements you can do to the current diff?

@ovflowd thanks for taking a look at the code. I could split the changes in from.js and the updated tests into a different PR, if that would help minimize the amount of code changes in this one.

In terms of the logic inside operators.js, it's not easy to view this as a diff because the previous logic almost entirely relied on createAsyncIterator which is in a different file readable.js and the asynchronous iteration engine that v8 provides. The new proposal is an implementation from scratch that keeps the same compatibility evaluated by the tests, but with improved performance. It's better to see both as black boxes implementations rather than trying to diff.

I could also split the changes into a few sections (maybe different commits?):

Functions that return a stream:

  • changes to drop, map, filter (drop depends on filter and filter depends on map).

I reiterate that flatMap and take have not been changed by this PR.

Functions that return a value:

  • changes to forEach, some, every, find. (the first two rely on the last)
  • changes to reduce
  • changes to toArray

…eadable
Signed-off-by: Luciano Leggieri <230980@gmail.com>
Assisted-by: Sol 5.6
 Signed-off-by: Luciano Leggieri <230980@gmail.com>
Assisted-by: Sol 5.6
 Signed-off-by: Luciano Leggieri <230980@gmail.com>
Assisted-by: Sol 5.6
…able.
Signed-off-by: Luciano Leggieri <230980@gmail.com>
Assisted-by: Sol 5.6
 Signed-off-by: Luciano Leggieri <230980@gmail.com>
Assisted-by: Sol 5.6
 Signed-off-by: Luciano Leggieri <230980@gmail.com>
@lukiano

Copy link
Copy Markdown
Author

I have split the changes into well-defined commits that should ease the review of each affected function.

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

Labels

large-prPRs subject to the large-PR policy.needs-ciPRs that need a full CI run.request-ciAdd this label to start a Jenkins CI on a PR.streamIssues and PRs related to Node.js streams.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@lukiano@nodejs-github-bot@ovflowd@aduh95
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Implementation of Readable iteration helpers that does not rely on Async Iteration - #64429

Open
lukiano wants to merge 6 commits into
nodejs:mainfrom
lukiano:chore/faster-map-2
Open

Implementation of Readable iteration helpers that does not rely on Async Iteration#64429
lukiano wants to merge 6 commits into
nodejs:mainfrom
lukiano:chore/faster-map-2

Conversation

@lukiano

@lukianolukiano commented Jul 11, 2026

Copy link
Copy Markdown

Hi, I'd like to offer an alternative implementation of Readable iteration helpers like find() that don't rely on for await (...) to consume items from the stream. This results in improved performance when the data is already available, such as when creating a Readable from an array, as shown in the attached screenshot (executed on a MacBook M2 Max).

The main con is increased duplication and code complexity. My understanding is that these helpers are still in the experimental phase, which I hope makes a change like this easier to accept.

There are many commits in the branch, but I'll squash them before merging.
Edit: I merged them to fix CI issues

The affected helpers are

  • map()
  • filter()
  • reduce()
  • find()
  • toArray()
  • some()
  • every()
  • drop()

Unaffected helpers:

  • flatmap()
  • take()

There's also a change to the from() method that increases the buffer watermark when the source is an array of data.
At first, I coded the changes manually, but eventually I had assistance from AI to keep backward compatibility. Still, there's a small break as can be seen in the updated test in test/parallel/test-stream-reduce.js.

The file benchmark/streams/operator-throughput.js allows interested parties to run the benchmarks on their computers or modify them to try other scenarios. I can remove it before merging.

stream-operator-throughput-summary

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/performance
  • @nodejs/streams

@nodejs-github-botnodejs-github-bot added needs-ci PRs that need a full CI run. stream Issues and PRs related to Node.js streams. labels Jul 11, 2026
@codecov

codecovBot commented Jul 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.99065% with 45 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.15%. Comparing base (9ad097b) to head (df55497).
⚠️ Report is 249 commits behind head on main.

Files with missing linesPatch %Lines
lib/internal/streams/operators.js92.90%43 Missing and 2 partials ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #64429 +/- ##
==========================================
- Coverage 90.33% 90.15% -0.19% 
==========================================
Files 751 751 Lines 250341 254051 +3710 Branches 47322 47897 +575 ==========================================
+ Hits 226145 229037 +2892 - Misses 15575 16276 +701 - Partials 8621 8738 +117 
Files with missing linesCoverage Δ
lib/internal/streams/from.js91.55% <100.00%> (+4.79%)⬆️
lib/internal/streams/operators.js94.11% <92.90%> (-2.04%)⬇️

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

@lukiano
lukianoforce-pushed the chore/faster-map-2 branch from fae33f0 to 17e0105CompareJuly 12, 2026 18:24
@lukiano

Copy link
Copy Markdown
Author

I'm seeing #64447, which may change the measured performance of the current implementation.

@lukiano
lukianoforce-pushed the chore/faster-map-2 branch from 17e0105 to 43335b2CompareJuly 15, 2026 05:49
@lukiano
lukianoforce-pushed the chore/faster-map-2 branch 3 times, most recently from e10c1f8 to 9050881CompareAugust 2, 2026 22:49
@lukiano

Copy link
Copy Markdown
Author

macbook-stream-operator-throughput-report.pdf
wsl2-stream-operator-throughput-report.pdf
linux-server-stream-operator-throughput-report.pdf
I've run the performance comparisons again with the latest changes from the main branch. macbook is the same M2 Max MacBook as before, linux-server is a 10900KF Intel CPU running on Debian 13, and wsl2 is a Windows machine with an AMD 9900x processor, and the tests ran within a WSL2 with Ubuntu 24.04

@lukiano
lukianoforce-pushed the chore/faster-map-2 branch 4 times, most recently from b3d857e to df55497CompareAugust 24, 2026 17:01
@lukiano

Copy link
Copy Markdown
Author

@nodejs/streams I know you guys have probably 20 million PRs to review but if you could add this one to the queue I'd really appreciate it :)

@aduh95aduh95 added the large-pr PRs subject to the large-PR policy. label Aug 28, 2026
@ovflowdovflowd added the request-ci Add this label to start a Jenkins CI on a PR. label Aug 29, 2026

@ovflowdovflowd left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The changes on streams/operators are quite significant, making the review of the code much harder. It'd be helpful if you could reorganize your changes in a way that minimizes diff. Or decouple current behavior from the added behavior of "data already being available"

My main concern is due to the nature of the diff, it is hard to see/visualize what actually was changed in terms of behavior/logic versus what is just moving code around. I'm not an expert on the streams implementation within node, but possibly my peers would argue the same.

Do you believe there any improvements you can do to the current diff?

@lukiano

lukiano commented Aug 29, 2026

Copy link
Copy Markdown
Author

The changes on streams/operators are quite significant, making the review of the code much harder. It'd be helpful if you could reorganize your changes in a way that minimizes diff. Or decouple current behavior from the added behavior of "data already being available"

My main concern is due to the nature of the diff, it is hard to see/visualize what actually was changed in terms of behavior/logic versus what is just moving code around. I'm not an expert on the streams implementation within node, but possibly my peers would argue the same.

Do you believe there any improvements you can do to the current diff?

@ovflowd thanks for taking a look at the code. I could split the changes in from.js and the updated tests into a different PR, if that would help minimize the amount of code changes in this one.

In terms of the logic inside operators.js, it's not easy to view this as a diff because the previous logic almost entirely relied on createAsyncIterator which is in a different file readable.js and the asynchronous iteration engine that v8 provides. The new proposal is an implementation from scratch that keeps the same compatibility evaluated by the tests, but with improved performance. It's better to see both as black boxes implementations rather than trying to diff.

I could also split the changes into a few sections (maybe different commits?):

Functions that return a stream:

  • changes to drop, map, filter (drop depends on filter and filter depends on map).

I reiterate that flatMap and take have not been changed by this PR.

Functions that return a value:

  • changes to forEach, some, every, find. (the first two rely on the last)
  • changes to reduce
  • changes to toArray

…eadable
Signed-off-by: Luciano Leggieri <230980@gmail.com>
Assisted-by: Sol 5.6
 Signed-off-by: Luciano Leggieri <230980@gmail.com>
Assisted-by: Sol 5.6
 Signed-off-by: Luciano Leggieri <230980@gmail.com>
Assisted-by: Sol 5.6
…able.
Signed-off-by: Luciano Leggieri <230980@gmail.com>
Assisted-by: Sol 5.6
 Signed-off-by: Luciano Leggieri <230980@gmail.com>
Assisted-by: Sol 5.6
 Signed-off-by: Luciano Leggieri <230980@gmail.com>
@lukiano

Copy link
Copy Markdown
Author

I have split the changes into well-defined commits that should ease the review of each affected function.

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

Labels

large-prPRs subject to the large-PR policy.needs-ciPRs that need a full CI run.request-ciAdd this label to start a Jenkins CI on a PR.streamIssues and PRs related to Node.js streams.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@lukiano@nodejs-github-bot@ovflowd@aduh95
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Implementation of Readable iteration helpers that does not rely on Async Iteration - #64429

Open
lukiano wants to merge 6 commits into
nodejs:mainfrom
lukiano:chore/faster-map-2
Open

Implementation of Readable iteration helpers that does not rely on Async Iteration#64429
lukiano wants to merge 6 commits into
nodejs:mainfrom
lukiano:chore/faster-map-2

Conversation

@lukiano

@lukianolukiano commented Jul 11, 2026

Copy link
Copy Markdown

Hi, I'd like to offer an alternative implementation of Readable iteration helpers like find() that don't rely on for await (...) to consume items from the stream. This results in improved performance when the data is already available, such as when creating a Readable from an array, as shown in the attached screenshot (executed on a MacBook M2 Max).

The main con is increased duplication and code complexity. My understanding is that these helpers are still in the experimental phase, which I hope makes a change like this easier to accept.

There are many commits in the branch, but I'll squash them before merging.
Edit: I merged them to fix CI issues

The affected helpers are

  • map()
  • filter()
  • reduce()
  • find()
  • toArray()
  • some()
  • every()
  • drop()

Unaffected helpers:

  • flatmap()
  • take()

There's also a change to the from() method that increases the buffer watermark when the source is an array of data.
At first, I coded the changes manually, but eventually I had assistance from AI to keep backward compatibility. Still, there's a small break as can be seen in the updated test in test/parallel/test-stream-reduce.js.

The file benchmark/streams/operator-throughput.js allows interested parties to run the benchmarks on their computers or modify them to try other scenarios. I can remove it before merging.

stream-operator-throughput-summary

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/performance
  • @nodejs/streams

@nodejs-github-botnodejs-github-bot added needs-ci PRs that need a full CI run. stream Issues and PRs related to Node.js streams. labels Jul 11, 2026
@codecov

codecovBot commented Jul 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.99065% with 45 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.15%. Comparing base (9ad097b) to head (df55497).
⚠️ Report is 249 commits behind head on main.

Files with missing linesPatch %Lines
lib/internal/streams/operators.js92.90%43 Missing and 2 partials ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #64429 +/- ##
==========================================
- Coverage 90.33% 90.15% -0.19% 
==========================================
Files 751 751 Lines 250341 254051 +3710 Branches 47322 47897 +575 ==========================================
+ Hits 226145 229037 +2892 - Misses 15575 16276 +701 - Partials 8621 8738 +117 
Files with missing linesCoverage Δ
lib/internal/streams/from.js91.55% <100.00%> (+4.79%)⬆️
lib/internal/streams/operators.js94.11% <92.90%> (-2.04%)⬇️

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

@lukiano
lukianoforce-pushed the chore/faster-map-2 branch from fae33f0 to 17e0105CompareJuly 12, 2026 18:24
@lukiano

Copy link
Copy Markdown
Author

I'm seeing #64447, which may change the measured performance of the current implementation.

@lukiano
lukianoforce-pushed the chore/faster-map-2 branch from 17e0105 to 43335b2CompareJuly 15, 2026 05:49
@lukiano
lukianoforce-pushed the chore/faster-map-2 branch 3 times, most recently from e10c1f8 to 9050881CompareAugust 2, 2026 22:49
@lukiano

Copy link
Copy Markdown
Author

macbook-stream-operator-throughput-report.pdf
wsl2-stream-operator-throughput-report.pdf
linux-server-stream-operator-throughput-report.pdf
I've run the performance comparisons again with the latest changes from the main branch. macbook is the same M2 Max MacBook as before, linux-server is a 10900KF Intel CPU running on Debian 13, and wsl2 is a Windows machine with an AMD 9900x processor, and the tests ran within a WSL2 with Ubuntu 24.04

@lukiano
lukianoforce-pushed the chore/faster-map-2 branch 4 times, most recently from b3d857e to df55497CompareAugust 24, 2026 17:01
@lukiano

Copy link
Copy Markdown
Author

@nodejs/streams I know you guys have probably 20 million PRs to review but if you could add this one to the queue I'd really appreciate it :)

@aduh95aduh95 added the large-pr PRs subject to the large-PR policy. label Aug 28, 2026
@ovflowdovflowd added the request-ci Add this label to start a Jenkins CI on a PR. label Aug 29, 2026

@ovflowdovflowd left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The changes on streams/operators are quite significant, making the review of the code much harder. It'd be helpful if you could reorganize your changes in a way that minimizes diff. Or decouple current behavior from the added behavior of "data already being available"

My main concern is due to the nature of the diff, it is hard to see/visualize what actually was changed in terms of behavior/logic versus what is just moving code around. I'm not an expert on the streams implementation within node, but possibly my peers would argue the same.

Do you believe there any improvements you can do to the current diff?

@lukiano

lukiano commented Aug 29, 2026

Copy link
Copy Markdown
Author

The changes on streams/operators are quite significant, making the review of the code much harder. It'd be helpful if you could reorganize your changes in a way that minimizes diff. Or decouple current behavior from the added behavior of "data already being available"

My main concern is due to the nature of the diff, it is hard to see/visualize what actually was changed in terms of behavior/logic versus what is just moving code around. I'm not an expert on the streams implementation within node, but possibly my peers would argue the same.

Do you believe there any improvements you can do to the current diff?

@ovflowd thanks for taking a look at the code. I could split the changes in from.js and the updated tests into a different PR, if that would help minimize the amount of code changes in this one.

In terms of the logic inside operators.js, it's not easy to view this as a diff because the previous logic almost entirely relied on createAsyncIterator which is in a different file readable.js and the asynchronous iteration engine that v8 provides. The new proposal is an implementation from scratch that keeps the same compatibility evaluated by the tests, but with improved performance. It's better to see both as black boxes implementations rather than trying to diff.

I could also split the changes into a few sections (maybe different commits?):

Functions that return a stream:

  • changes to drop, map, filter (drop depends on filter and filter depends on map).

I reiterate that flatMap and take have not been changed by this PR.

Functions that return a value:

  • changes to forEach, some, every, find. (the first two rely on the last)
  • changes to reduce
  • changes to toArray

…eadable
Signed-off-by: Luciano Leggieri <230980@gmail.com>
Assisted-by: Sol 5.6
 Signed-off-by: Luciano Leggieri <230980@gmail.com>
Assisted-by: Sol 5.6
 Signed-off-by: Luciano Leggieri <230980@gmail.com>
Assisted-by: Sol 5.6
…able.
Signed-off-by: Luciano Leggieri <230980@gmail.com>
Assisted-by: Sol 5.6
 Signed-off-by: Luciano Leggieri <230980@gmail.com>
Assisted-by: Sol 5.6
 Signed-off-by: Luciano Leggieri <230980@gmail.com>
@lukiano

Copy link
Copy Markdown
Author

I have split the changes into well-defined commits that should ease the review of each affected function.

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

Labels

large-prPRs subject to the large-PR policy.needs-ciPRs that need a full CI run.request-ciAdd this label to start a Jenkins CI on a PR.streamIssues and PRs related to Node.js streams.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@lukiano@nodejs-github-bot@ovflowd@aduh95
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Implementation of Readable iteration helpers that does not rely on Async Iteration - #64429

Open
lukiano wants to merge 6 commits into
nodejs:mainfrom
lukiano:chore/faster-map-2
Open

Implementation of Readable iteration helpers that does not rely on Async Iteration#64429
lukiano wants to merge 6 commits into
nodejs:mainfrom
lukiano:chore/faster-map-2

Conversation

@lukiano

@lukianolukiano commented Jul 11, 2026

Copy link
Copy Markdown

Hi, I'd like to offer an alternative implementation of Readable iteration helpers like find() that don't rely on for await (...) to consume items from the stream. This results in improved performance when the data is already available, such as when creating a Readable from an array, as shown in the attached screenshot (executed on a MacBook M2 Max).

The main con is increased duplication and code complexity. My understanding is that these helpers are still in the experimental phase, which I hope makes a change like this easier to accept.

There are many commits in the branch, but I'll squash them before merging.
Edit: I merged them to fix CI issues

The affected helpers are

  • map()
  • filter()
  • reduce()
  • find()
  • toArray()
  • some()
  • every()
  • drop()

Unaffected helpers:

  • flatmap()
  • take()

There's also a change to the from() method that increases the buffer watermark when the source is an array of data.
At first, I coded the changes manually, but eventually I had assistance from AI to keep backward compatibility. Still, there's a small break as can be seen in the updated test in test/parallel/test-stream-reduce.js.

The file benchmark/streams/operator-throughput.js allows interested parties to run the benchmarks on their computers or modify them to try other scenarios. I can remove it before merging.

stream-operator-throughput-summary

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/performance
  • @nodejs/streams

@nodejs-github-botnodejs-github-bot added needs-ci PRs that need a full CI run. stream Issues and PRs related to Node.js streams. labels Jul 11, 2026
@codecov

codecovBot commented Jul 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.99065% with 45 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.15%. Comparing base (9ad097b) to head (df55497).
⚠️ Report is 249 commits behind head on main.

Files with missing linesPatch %Lines
lib/internal/streams/operators.js92.90%43 Missing and 2 partials ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #64429 +/- ##
==========================================
- Coverage 90.33% 90.15% -0.19% 
==========================================
Files 751 751 Lines 250341 254051 +3710 Branches 47322 47897 +575 ==========================================
+ Hits 226145 229037 +2892 - Misses 15575 16276 +701 - Partials 8621 8738 +117 
Files with missing linesCoverage Δ
lib/internal/streams/from.js91.55% <100.00%> (+4.79%)⬆️
lib/internal/streams/operators.js94.11% <92.90%> (-2.04%)⬇️

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

@lukiano
lukianoforce-pushed the chore/faster-map-2 branch from fae33f0 to 17e0105CompareJuly 12, 2026 18:24
@lukiano

Copy link
Copy Markdown
Author

I'm seeing #64447, which may change the measured performance of the current implementation.

@lukiano
lukianoforce-pushed the chore/faster-map-2 branch from 17e0105 to 43335b2CompareJuly 15, 2026 05:49
@lukiano
lukianoforce-pushed the chore/faster-map-2 branch 3 times, most recently from e10c1f8 to 9050881CompareAugust 2, 2026 22:49
@lukiano

Copy link
Copy Markdown
Author

macbook-stream-operator-throughput-report.pdf
wsl2-stream-operator-throughput-report.pdf
linux-server-stream-operator-throughput-report.pdf
I've run the performance comparisons again with the latest changes from the main branch. macbook is the same M2 Max MacBook as before, linux-server is a 10900KF Intel CPU running on Debian 13, and wsl2 is a Windows machine with an AMD 9900x processor, and the tests ran within a WSL2 with Ubuntu 24.04

@lukiano
lukianoforce-pushed the chore/faster-map-2 branch 4 times, most recently from b3d857e to df55497CompareAugust 24, 2026 17:01
@lukiano

Copy link
Copy Markdown
Author

@nodejs/streams I know you guys have probably 20 million PRs to review but if you could add this one to the queue I'd really appreciate it :)

@aduh95aduh95 added the large-pr PRs subject to the large-PR policy. label Aug 28, 2026
@ovflowdovflowd added the request-ci Add this label to start a Jenkins CI on a PR. label Aug 29, 2026

@ovflowdovflowd left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The changes on streams/operators are quite significant, making the review of the code much harder. It'd be helpful if you could reorganize your changes in a way that minimizes diff. Or decouple current behavior from the added behavior of "data already being available"

My main concern is due to the nature of the diff, it is hard to see/visualize what actually was changed in terms of behavior/logic versus what is just moving code around. I'm not an expert on the streams implementation within node, but possibly my peers would argue the same.

Do you believe there any improvements you can do to the current diff?

@lukiano

lukiano commented Aug 29, 2026

Copy link
Copy Markdown
Author

The changes on streams/operators are quite significant, making the review of the code much harder. It'd be helpful if you could reorganize your changes in a way that minimizes diff. Or decouple current behavior from the added behavior of "data already being available"

My main concern is due to the nature of the diff, it is hard to see/visualize what actually was changed in terms of behavior/logic versus what is just moving code around. I'm not an expert on the streams implementation within node, but possibly my peers would argue the same.

Do you believe there any improvements you can do to the current diff?

@ovflowd thanks for taking a look at the code. I could split the changes in from.js and the updated tests into a different PR, if that would help minimize the amount of code changes in this one.

In terms of the logic inside operators.js, it's not easy to view this as a diff because the previous logic almost entirely relied on createAsyncIterator which is in a different file readable.js and the asynchronous iteration engine that v8 provides. The new proposal is an implementation from scratch that keeps the same compatibility evaluated by the tests, but with improved performance. It's better to see both as black boxes implementations rather than trying to diff.

I could also split the changes into a few sections (maybe different commits?):

Functions that return a stream:

  • changes to drop, map, filter (drop depends on filter and filter depends on map).

I reiterate that flatMap and take have not been changed by this PR.

Functions that return a value:

  • changes to forEach, some, every, find. (the first two rely on the last)
  • changes to reduce
  • changes to toArray

…eadable
Signed-off-by: Luciano Leggieri <230980@gmail.com>
Assisted-by: Sol 5.6
 Signed-off-by: Luciano Leggieri <230980@gmail.com>
Assisted-by: Sol 5.6
 Signed-off-by: Luciano Leggieri <230980@gmail.com>
Assisted-by: Sol 5.6
…able.
Signed-off-by: Luciano Leggieri <230980@gmail.com>
Assisted-by: Sol 5.6
 Signed-off-by: Luciano Leggieri <230980@gmail.com>
Assisted-by: Sol 5.6
 Signed-off-by: Luciano Leggieri <230980@gmail.com>
@lukiano

Copy link
Copy Markdown
Author

I have split the changes into well-defined commits that should ease the review of each affected function.

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

Labels

large-prPRs subject to the large-PR policy.needs-ciPRs that need a full CI run.request-ciAdd this label to start a Jenkins CI on a PR.streamIssues and PRs related to Node.js streams.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@lukiano@nodejs-github-bot@ovflowd@aduh95
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Implementation of Readable iteration helpers that does not rely on Async Iteration - #64429

Open
lukiano wants to merge 6 commits into
nodejs:mainfrom
lukiano:chore/faster-map-2
Open

Implementation of Readable iteration helpers that does not rely on Async Iteration#64429
lukiano wants to merge 6 commits into
nodejs:mainfrom
lukiano:chore/faster-map-2

Conversation

@lukiano

@lukianolukiano commented Jul 11, 2026

Copy link
Copy Markdown

Hi, I'd like to offer an alternative implementation of Readable iteration helpers like find() that don't rely on for await (...) to consume items from the stream. This results in improved performance when the data is already available, such as when creating a Readable from an array, as shown in the attached screenshot (executed on a MacBook M2 Max).

The main con is increased duplication and code complexity. My understanding is that these helpers are still in the experimental phase, which I hope makes a change like this easier to accept.

There are many commits in the branch, but I'll squash them before merging.
Edit: I merged them to fix CI issues

The affected helpers are

  • map()
  • filter()
  • reduce()
  • find()
  • toArray()
  • some()
  • every()
  • drop()

Unaffected helpers:

  • flatmap()
  • take()

There's also a change to the from() method that increases the buffer watermark when the source is an array of data.
At first, I coded the changes manually, but eventually I had assistance from AI to keep backward compatibility. Still, there's a small break as can be seen in the updated test in test/parallel/test-stream-reduce.js.

The file benchmark/streams/operator-throughput.js allows interested parties to run the benchmarks on their computers or modify them to try other scenarios. I can remove it before merging.

stream-operator-throughput-summary

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/performance
  • @nodejs/streams

@nodejs-github-botnodejs-github-bot added needs-ci PRs that need a full CI run. stream Issues and PRs related to Node.js streams. labels Jul 11, 2026
@codecov

codecovBot commented Jul 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.99065% with 45 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.15%. Comparing base (9ad097b) to head (df55497).
⚠️ Report is 249 commits behind head on main.

Files with missing linesPatch %Lines
lib/internal/streams/operators.js92.90%43 Missing and 2 partials ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #64429 +/- ##
==========================================
- Coverage 90.33% 90.15% -0.19% 
==========================================
Files 751 751 Lines 250341 254051 +3710 Branches 47322 47897 +575 ==========================================
+ Hits 226145 229037 +2892 - Misses 15575 16276 +701 - Partials 8621 8738 +117 
Files with missing linesCoverage Δ
lib/internal/streams/from.js91.55% <100.00%> (+4.79%)⬆️
lib/internal/streams/operators.js94.11% <92.90%> (-2.04%)⬇️

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

@lukiano
lukianoforce-pushed the chore/faster-map-2 branch from fae33f0 to 17e0105CompareJuly 12, 2026 18:24
@lukiano

Copy link
Copy Markdown
Author

I'm seeing #64447, which may change the measured performance of the current implementation.

@lukiano
lukianoforce-pushed the chore/faster-map-2 branch from 17e0105 to 43335b2CompareJuly 15, 2026 05:49
@lukiano
lukianoforce-pushed the chore/faster-map-2 branch 3 times, most recently from e10c1f8 to 9050881CompareAugust 2, 2026 22:49
@lukiano

Copy link
Copy Markdown
Author

macbook-stream-operator-throughput-report.pdf
wsl2-stream-operator-throughput-report.pdf
linux-server-stream-operator-throughput-report.pdf
I've run the performance comparisons again with the latest changes from the main branch. macbook is the same M2 Max MacBook as before, linux-server is a 10900KF Intel CPU running on Debian 13, and wsl2 is a Windows machine with an AMD 9900x processor, and the tests ran within a WSL2 with Ubuntu 24.04

@lukiano
lukianoforce-pushed the chore/faster-map-2 branch 4 times, most recently from b3d857e to df55497CompareAugust 24, 2026 17:01
@lukiano

Copy link
Copy Markdown
Author

@nodejs/streams I know you guys have probably 20 million PRs to review but if you could add this one to the queue I'd really appreciate it :)

@aduh95aduh95 added the large-pr PRs subject to the large-PR policy. label Aug 28, 2026
@ovflowdovflowd added the request-ci Add this label to start a Jenkins CI on a PR. label Aug 29, 2026

@ovflowdovflowd left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The changes on streams/operators are quite significant, making the review of the code much harder. It'd be helpful if you could reorganize your changes in a way that minimizes diff. Or decouple current behavior from the added behavior of "data already being available"

My main concern is due to the nature of the diff, it is hard to see/visualize what actually was changed in terms of behavior/logic versus what is just moving code around. I'm not an expert on the streams implementation within node, but possibly my peers would argue the same.

Do you believe there any improvements you can do to the current diff?

@lukiano

lukiano commented Aug 29, 2026

Copy link
Copy Markdown
Author

The changes on streams/operators are quite significant, making the review of the code much harder. It'd be helpful if you could reorganize your changes in a way that minimizes diff. Or decouple current behavior from the added behavior of "data already being available"

My main concern is due to the nature of the diff, it is hard to see/visualize what actually was changed in terms of behavior/logic versus what is just moving code around. I'm not an expert on the streams implementation within node, but possibly my peers would argue the same.

Do you believe there any improvements you can do to the current diff?

@ovflowd thanks for taking a look at the code. I could split the changes in from.js and the updated tests into a different PR, if that would help minimize the amount of code changes in this one.

In terms of the logic inside operators.js, it's not easy to view this as a diff because the previous logic almost entirely relied on createAsyncIterator which is in a different file readable.js and the asynchronous iteration engine that v8 provides. The new proposal is an implementation from scratch that keeps the same compatibility evaluated by the tests, but with improved performance. It's better to see both as black boxes implementations rather than trying to diff.

I could also split the changes into a few sections (maybe different commits?):

Functions that return a stream:

  • changes to drop, map, filter (drop depends on filter and filter depends on map).

I reiterate that flatMap and take have not been changed by this PR.

Functions that return a value:

  • changes to forEach, some, every, find. (the first two rely on the last)
  • changes to reduce
  • changes to toArray

…eadable
Signed-off-by: Luciano Leggieri <230980@gmail.com>
Assisted-by: Sol 5.6
 Signed-off-by: Luciano Leggieri <230980@gmail.com>
Assisted-by: Sol 5.6
 Signed-off-by: Luciano Leggieri <230980@gmail.com>
Assisted-by: Sol 5.6
…able.
Signed-off-by: Luciano Leggieri <230980@gmail.com>
Assisted-by: Sol 5.6
 Signed-off-by: Luciano Leggieri <230980@gmail.com>
Assisted-by: Sol 5.6
 Signed-off-by: Luciano Leggieri <230980@gmail.com>
@lukiano

Copy link
Copy Markdown
Author

I have split the changes into well-defined commits that should ease the review of each affected function.

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

Labels

large-prPRs subject to the large-PR policy.needs-ciPRs that need a full CI run.request-ciAdd this label to start a Jenkins CI on a PR.streamIssues and PRs related to Node.js streams.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@lukiano@nodejs-github-bot@ovflowd@aduh95
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Implementation of Readable iteration helpers that does not rely on Async Iteration - #64429

Open
lukiano wants to merge 6 commits into
nodejs:mainfrom
lukiano:chore/faster-map-2
Open

Implementation of Readable iteration helpers that does not rely on Async Iteration#64429
lukiano wants to merge 6 commits into
nodejs:mainfrom
lukiano:chore/faster-map-2

Conversation

@lukiano

@lukianolukiano commented Jul 11, 2026

Copy link
Copy Markdown

Hi, I'd like to offer an alternative implementation of Readable iteration helpers like find() that don't rely on for await (...) to consume items from the stream. This results in improved performance when the data is already available, such as when creating a Readable from an array, as shown in the attached screenshot (executed on a MacBook M2 Max).

The main con is increased duplication and code complexity. My understanding is that these helpers are still in the experimental phase, which I hope makes a change like this easier to accept.

There are many commits in the branch, but I'll squash them before merging.
Edit: I merged them to fix CI issues

The affected helpers are

  • map()
  • filter()
  • reduce()
  • find()
  • toArray()
  • some()
  • every()
  • drop()

Unaffected helpers:

  • flatmap()
  • take()

There's also a change to the from() method that increases the buffer watermark when the source is an array of data.
At first, I coded the changes manually, but eventually I had assistance from AI to keep backward compatibility. Still, there's a small break as can be seen in the updated test in test/parallel/test-stream-reduce.js.

The file benchmark/streams/operator-throughput.js allows interested parties to run the benchmarks on their computers or modify them to try other scenarios. I can remove it before merging.

stream-operator-throughput-summary

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/performance
  • @nodejs/streams

@nodejs-github-botnodejs-github-bot added needs-ci PRs that need a full CI run. stream Issues and PRs related to Node.js streams. labels Jul 11, 2026
@codecov

codecovBot commented Jul 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.99065% with 45 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.15%. Comparing base (9ad097b) to head (df55497).
⚠️ Report is 249 commits behind head on main.

Files with missing linesPatch %Lines
lib/internal/streams/operators.js92.90%43 Missing and 2 partials ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #64429 +/- ##
==========================================
- Coverage 90.33% 90.15% -0.19% 
==========================================
Files 751 751 Lines 250341 254051 +3710 Branches 47322 47897 +575 ==========================================
+ Hits 226145 229037 +2892 - Misses 15575 16276 +701 - Partials 8621 8738 +117 
Files with missing linesCoverage Δ
lib/internal/streams/from.js91.55% <100.00%> (+4.79%)⬆️
lib/internal/streams/operators.js94.11% <92.90%> (-2.04%)⬇️

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

@lukiano
lukianoforce-pushed the chore/faster-map-2 branch from fae33f0 to 17e0105CompareJuly 12, 2026 18:24
@lukiano

Copy link
Copy Markdown
Author

I'm seeing #64447, which may change the measured performance of the current implementation.

@lukiano
lukianoforce-pushed the chore/faster-map-2 branch from 17e0105 to 43335b2CompareJuly 15, 2026 05:49
@lukiano
lukianoforce-pushed the chore/faster-map-2 branch 3 times, most recently from e10c1f8 to 9050881CompareAugust 2, 2026 22:49
@lukiano

Copy link
Copy Markdown
Author

macbook-stream-operator-throughput-report.pdf
wsl2-stream-operator-throughput-report.pdf
linux-server-stream-operator-throughput-report.pdf
I've run the performance comparisons again with the latest changes from the main branch. macbook is the same M2 Max MacBook as before, linux-server is a 10900KF Intel CPU running on Debian 13, and wsl2 is a Windows machine with an AMD 9900x processor, and the tests ran within a WSL2 with Ubuntu 24.04

@lukiano
lukianoforce-pushed the chore/faster-map-2 branch 4 times, most recently from b3d857e to df55497CompareAugust 24, 2026 17:01
@lukiano

Copy link
Copy Markdown
Author

@nodejs/streams I know you guys have probably 20 million PRs to review but if you could add this one to the queue I'd really appreciate it :)

@aduh95aduh95 added the large-pr PRs subject to the large-PR policy. label Aug 28, 2026
@ovflowdovflowd added the request-ci Add this label to start a Jenkins CI on a PR. label Aug 29, 2026

@ovflowdovflowd left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The changes on streams/operators are quite significant, making the review of the code much harder. It'd be helpful if you could reorganize your changes in a way that minimizes diff. Or decouple current behavior from the added behavior of "data already being available"

My main concern is due to the nature of the diff, it is hard to see/visualize what actually was changed in terms of behavior/logic versus what is just moving code around. I'm not an expert on the streams implementation within node, but possibly my peers would argue the same.

Do you believe there any improvements you can do to the current diff?

@lukiano

lukiano commented Aug 29, 2026

Copy link
Copy Markdown
Author

The changes on streams/operators are quite significant, making the review of the code much harder. It'd be helpful if you could reorganize your changes in a way that minimizes diff. Or decouple current behavior from the added behavior of "data already being available"

My main concern is due to the nature of the diff, it is hard to see/visualize what actually was changed in terms of behavior/logic versus what is just moving code around. I'm not an expert on the streams implementation within node, but possibly my peers would argue the same.

Do you believe there any improvements you can do to the current diff?

@ovflowd thanks for taking a look at the code. I could split the changes in from.js and the updated tests into a different PR, if that would help minimize the amount of code changes in this one.

In terms of the logic inside operators.js, it's not easy to view this as a diff because the previous logic almost entirely relied on createAsyncIterator which is in a different file readable.js and the asynchronous iteration engine that v8 provides. The new proposal is an implementation from scratch that keeps the same compatibility evaluated by the tests, but with improved performance. It's better to see both as black boxes implementations rather than trying to diff.

I could also split the changes into a few sections (maybe different commits?):

Functions that return a stream:

  • changes to drop, map, filter (drop depends on filter and filter depends on map).

I reiterate that flatMap and take have not been changed by this PR.

Functions that return a value:

  • changes to forEach, some, every, find. (the first two rely on the last)
  • changes to reduce
  • changes to toArray

…eadable
Signed-off-by: Luciano Leggieri <230980@gmail.com>
Assisted-by: Sol 5.6
 Signed-off-by: Luciano Leggieri <230980@gmail.com>
Assisted-by: Sol 5.6
 Signed-off-by: Luciano Leggieri <230980@gmail.com>
Assisted-by: Sol 5.6
…able.
Signed-off-by: Luciano Leggieri <230980@gmail.com>
Assisted-by: Sol 5.6
 Signed-off-by: Luciano Leggieri <230980@gmail.com>
Assisted-by: Sol 5.6
 Signed-off-by: Luciano Leggieri <230980@gmail.com>
@lukiano

Copy link
Copy Markdown
Author

I have split the changes into well-defined commits that should ease the review of each affected function.

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

Labels

large-prPRs subject to the large-PR policy.needs-ciPRs that need a full CI run.request-ciAdd this label to start a Jenkins CI on a PR.streamIssues and PRs related to Node.js streams.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@lukiano@nodejs-github-bot@ovflowd@aduh95
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Implementation of Readable iteration helpers that does not rely on Async Iteration - #64429

Open
lukiano wants to merge 6 commits into
nodejs:mainfrom
lukiano:chore/faster-map-2
Open

Implementation of Readable iteration helpers that does not rely on Async Iteration#64429
lukiano wants to merge 6 commits into
nodejs:mainfrom
lukiano:chore/faster-map-2

Conversation

@lukiano

@lukianolukiano commented Jul 11, 2026

Copy link
Copy Markdown

Hi, I'd like to offer an alternative implementation of Readable iteration helpers like find() that don't rely on for await (...) to consume items from the stream. This results in improved performance when the data is already available, such as when creating a Readable from an array, as shown in the attached screenshot (executed on a MacBook M2 Max).

The main con is increased duplication and code complexity. My understanding is that these helpers are still in the experimental phase, which I hope makes a change like this easier to accept.

There are many commits in the branch, but I'll squash them before merging.
Edit: I merged them to fix CI issues

The affected helpers are

  • map()
  • filter()
  • reduce()
  • find()
  • toArray()
  • some()
  • every()
  • drop()

Unaffected helpers:

  • flatmap()
  • take()

There's also a change to the from() method that increases the buffer watermark when the source is an array of data.
At first, I coded the changes manually, but eventually I had assistance from AI to keep backward compatibility. Still, there's a small break as can be seen in the updated test in test/parallel/test-stream-reduce.js.

The file benchmark/streams/operator-throughput.js allows interested parties to run the benchmarks on their computers or modify them to try other scenarios. I can remove it before merging.

stream-operator-throughput-summary

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/performance
  • @nodejs/streams

@nodejs-github-botnodejs-github-bot added needs-ci PRs that need a full CI run. stream Issues and PRs related to Node.js streams. labels Jul 11, 2026
@codecov

codecovBot commented Jul 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.99065% with 45 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.15%. Comparing base (9ad097b) to head (df55497).
⚠️ Report is 249 commits behind head on main.

Files with missing linesPatch %Lines
lib/internal/streams/operators.js92.90%43 Missing and 2 partials ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #64429 +/- ##
==========================================
- Coverage 90.33% 90.15% -0.19% 
==========================================
Files 751 751 Lines 250341 254051 +3710 Branches 47322 47897 +575 ==========================================
+ Hits 226145 229037 +2892 - Misses 15575 16276 +701 - Partials 8621 8738 +117 
Files with missing linesCoverage Δ
lib/internal/streams/from.js91.55% <100.00%> (+4.79%)⬆️
lib/internal/streams/operators.js94.11% <92.90%> (-2.04%)⬇️

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

@lukiano
lukianoforce-pushed the chore/faster-map-2 branch from fae33f0 to 17e0105CompareJuly 12, 2026 18:24
@lukiano

Copy link
Copy Markdown
Author

I'm seeing #64447, which may change the measured performance of the current implementation.

@lukiano
lukianoforce-pushed the chore/faster-map-2 branch from 17e0105 to 43335b2CompareJuly 15, 2026 05:49
@lukiano
lukianoforce-pushed the chore/faster-map-2 branch 3 times, most recently from e10c1f8 to 9050881CompareAugust 2, 2026 22:49
@lukiano

Copy link
Copy Markdown
Author

macbook-stream-operator-throughput-report.pdf
wsl2-stream-operator-throughput-report.pdf
linux-server-stream-operator-throughput-report.pdf
I've run the performance comparisons again with the latest changes from the main branch. macbook is the same M2 Max MacBook as before, linux-server is a 10900KF Intel CPU running on Debian 13, and wsl2 is a Windows machine with an AMD 9900x processor, and the tests ran within a WSL2 with Ubuntu 24.04

@lukiano
lukianoforce-pushed the chore/faster-map-2 branch 4 times, most recently from b3d857e to df55497CompareAugust 24, 2026 17:01
@lukiano

Copy link
Copy Markdown
Author

@nodejs/streams I know you guys have probably 20 million PRs to review but if you could add this one to the queue I'd really appreciate it :)

@aduh95aduh95 added the large-pr PRs subject to the large-PR policy. label Aug 28, 2026
@ovflowdovflowd added the request-ci Add this label to start a Jenkins CI on a PR. label Aug 29, 2026

@ovflowdovflowd left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The changes on streams/operators are quite significant, making the review of the code much harder. It'd be helpful if you could reorganize your changes in a way that minimizes diff. Or decouple current behavior from the added behavior of "data already being available"

My main concern is due to the nature of the diff, it is hard to see/visualize what actually was changed in terms of behavior/logic versus what is just moving code around. I'm not an expert on the streams implementation within node, but possibly my peers would argue the same.

Do you believe there any improvements you can do to the current diff?

@lukiano

lukiano commented Aug 29, 2026

Copy link
Copy Markdown
Author

The changes on streams/operators are quite significant, making the review of the code much harder. It'd be helpful if you could reorganize your changes in a way that minimizes diff. Or decouple current behavior from the added behavior of "data already being available"

My main concern is due to the nature of the diff, it is hard to see/visualize what actually was changed in terms of behavior/logic versus what is just moving code around. I'm not an expert on the streams implementation within node, but possibly my peers would argue the same.

Do you believe there any improvements you can do to the current diff?

@ovflowd thanks for taking a look at the code. I could split the changes in from.js and the updated tests into a different PR, if that would help minimize the amount of code changes in this one.

In terms of the logic inside operators.js, it's not easy to view this as a diff because the previous logic almost entirely relied on createAsyncIterator which is in a different file readable.js and the asynchronous iteration engine that v8 provides. The new proposal is an implementation from scratch that keeps the same compatibility evaluated by the tests, but with improved performance. It's better to see both as black boxes implementations rather than trying to diff.

I could also split the changes into a few sections (maybe different commits?):

Functions that return a stream:

  • changes to drop, map, filter (drop depends on filter and filter depends on map).

I reiterate that flatMap and take have not been changed by this PR.

Functions that return a value:

  • changes to forEach, some, every, find. (the first two rely on the last)
  • changes to reduce
  • changes to toArray

…eadable
Signed-off-by: Luciano Leggieri <230980@gmail.com>
Assisted-by: Sol 5.6
 Signed-off-by: Luciano Leggieri <230980@gmail.com>
Assisted-by: Sol 5.6
 Signed-off-by: Luciano Leggieri <230980@gmail.com>
Assisted-by: Sol 5.6
…able.
Signed-off-by: Luciano Leggieri <230980@gmail.com>
Assisted-by: Sol 5.6
 Signed-off-by: Luciano Leggieri <230980@gmail.com>
Assisted-by: Sol 5.6
 Signed-off-by: Luciano Leggieri <230980@gmail.com>
@lukiano

Copy link
Copy Markdown
Author

I have split the changes into well-defined commits that should ease the review of each affected function.

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

Labels

large-prPRs subject to the large-PR policy.needs-ciPRs that need a full CI run.request-ciAdd this label to start a Jenkins CI on a PR.streamIssues and PRs related to Node.js streams.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@lukiano@nodejs-github-bot@ovflowd@aduh95
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Implementation of Readable iteration helpers that does not rely on Async Iteration - #64429

Open
lukiano wants to merge 6 commits into
nodejs:mainfrom
lukiano:chore/faster-map-2
Open

Implementation of Readable iteration helpers that does not rely on Async Iteration#64429
lukiano wants to merge 6 commits into
nodejs:mainfrom
lukiano:chore/faster-map-2

Conversation

@lukiano

@lukianolukiano commented Jul 11, 2026

Copy link
Copy Markdown

Hi, I'd like to offer an alternative implementation of Readable iteration helpers like find() that don't rely on for await (...) to consume items from the stream. This results in improved performance when the data is already available, such as when creating a Readable from an array, as shown in the attached screenshot (executed on a MacBook M2 Max).

The main con is increased duplication and code complexity. My understanding is that these helpers are still in the experimental phase, which I hope makes a change like this easier to accept.

There are many commits in the branch, but I'll squash them before merging.
Edit: I merged them to fix CI issues

The affected helpers are

  • map()
  • filter()
  • reduce()
  • find()
  • toArray()
  • some()
  • every()
  • drop()

Unaffected helpers:

  • flatmap()
  • take()

There's also a change to the from() method that increases the buffer watermark when the source is an array of data.
At first, I coded the changes manually, but eventually I had assistance from AI to keep backward compatibility. Still, there's a small break as can be seen in the updated test in test/parallel/test-stream-reduce.js.

The file benchmark/streams/operator-throughput.js allows interested parties to run the benchmarks on their computers or modify them to try other scenarios. I can remove it before merging.

stream-operator-throughput-summary

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/performance
  • @nodejs/streams

@nodejs-github-botnodejs-github-bot added needs-ci PRs that need a full CI run. stream Issues and PRs related to Node.js streams. labels Jul 11, 2026
@codecov

codecovBot commented Jul 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.99065% with 45 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.15%. Comparing base (9ad097b) to head (df55497).
⚠️ Report is 249 commits behind head on main.

Files with missing linesPatch %Lines
lib/internal/streams/operators.js92.90%43 Missing and 2 partials ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #64429 +/- ##
==========================================
- Coverage 90.33% 90.15% -0.19% 
==========================================
Files 751 751 Lines 250341 254051 +3710 Branches 47322 47897 +575 ==========================================
+ Hits 226145 229037 +2892 - Misses 15575 16276 +701 - Partials 8621 8738 +117 
Files with missing linesCoverage Δ
lib/internal/streams/from.js91.55% <100.00%> (+4.79%)⬆️
lib/internal/streams/operators.js94.11% <92.90%> (-2.04%)⬇️

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

@lukiano
lukianoforce-pushed the chore/faster-map-2 branch from fae33f0 to 17e0105CompareJuly 12, 2026 18:24
@lukiano

Copy link
Copy Markdown
Author

I'm seeing #64447, which may change the measured performance of the current implementation.

@lukiano
lukianoforce-pushed the chore/faster-map-2 branch from 17e0105 to 43335b2CompareJuly 15, 2026 05:49
@lukiano
lukianoforce-pushed the chore/faster-map-2 branch 3 times, most recently from e10c1f8 to 9050881CompareAugust 2, 2026 22:49
@lukiano

Copy link
Copy Markdown
Author

macbook-stream-operator-throughput-report.pdf
wsl2-stream-operator-throughput-report.pdf
linux-server-stream-operator-throughput-report.pdf
I've run the performance comparisons again with the latest changes from the main branch. macbook is the same M2 Max MacBook as before, linux-server is a 10900KF Intel CPU running on Debian 13, and wsl2 is a Windows machine with an AMD 9900x processor, and the tests ran within a WSL2 with Ubuntu 24.04

@lukiano
lukianoforce-pushed the chore/faster-map-2 branch 4 times, most recently from b3d857e to df55497CompareAugust 24, 2026 17:01
@lukiano

Copy link
Copy Markdown
Author

@nodejs/streams I know you guys have probably 20 million PRs to review but if you could add this one to the queue I'd really appreciate it :)

@aduh95aduh95 added the large-pr PRs subject to the large-PR policy. label Aug 28, 2026
@ovflowdovflowd added the request-ci Add this label to start a Jenkins CI on a PR. label Aug 29, 2026

@ovflowdovflowd left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The changes on streams/operators are quite significant, making the review of the code much harder. It'd be helpful if you could reorganize your changes in a way that minimizes diff. Or decouple current behavior from the added behavior of "data already being available"

My main concern is due to the nature of the diff, it is hard to see/visualize what actually was changed in terms of behavior/logic versus what is just moving code around. I'm not an expert on the streams implementation within node, but possibly my peers would argue the same.

Do you believe there any improvements you can do to the current diff?

@lukiano

lukiano commented Aug 29, 2026

Copy link
Copy Markdown
Author

The changes on streams/operators are quite significant, making the review of the code much harder. It'd be helpful if you could reorganize your changes in a way that minimizes diff. Or decouple current behavior from the added behavior of "data already being available"

My main concern is due to the nature of the diff, it is hard to see/visualize what actually was changed in terms of behavior/logic versus what is just moving code around. I'm not an expert on the streams implementation within node, but possibly my peers would argue the same.

Do you believe there any improvements you can do to the current diff?

@ovflowd thanks for taking a look at the code. I could split the changes in from.js and the updated tests into a different PR, if that would help minimize the amount of code changes in this one.

In terms of the logic inside operators.js, it's not easy to view this as a diff because the previous logic almost entirely relied on createAsyncIterator which is in a different file readable.js and the asynchronous iteration engine that v8 provides. The new proposal is an implementation from scratch that keeps the same compatibility evaluated by the tests, but with improved performance. It's better to see both as black boxes implementations rather than trying to diff.

I could also split the changes into a few sections (maybe different commits?):

Functions that return a stream:

  • changes to drop, map, filter (drop depends on filter and filter depends on map).

I reiterate that flatMap and take have not been changed by this PR.

Functions that return a value:

  • changes to forEach, some, every, find. (the first two rely on the last)
  • changes to reduce
  • changes to toArray

…eadable
Signed-off-by: Luciano Leggieri <230980@gmail.com>
Assisted-by: Sol 5.6
 Signed-off-by: Luciano Leggieri <230980@gmail.com>
Assisted-by: Sol 5.6
 Signed-off-by: Luciano Leggieri <230980@gmail.com>
Assisted-by: Sol 5.6
…able.
Signed-off-by: Luciano Leggieri <230980@gmail.com>
Assisted-by: Sol 5.6
 Signed-off-by: Luciano Leggieri <230980@gmail.com>
Assisted-by: Sol 5.6
 Signed-off-by: Luciano Leggieri <230980@gmail.com>
@lukiano

Copy link
Copy Markdown
Author

I have split the changes into well-defined commits that should ease the review of each affected function.

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

Labels

large-prPRs subject to the large-PR policy.needs-ciPRs that need a full CI run.request-ciAdd this label to start a Jenkins CI on a PR.streamIssues and PRs related to Node.js streams.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@lukiano@nodejs-github-bot@ovflowd@aduh95