Commit 724190cfdcc for nodejs
commit 724190cfdccd3a4d2e2dca5b0c5de188d04e39db
Author: James M Snell <jasnell@gmail.com>
Date: Sun Oct 4 02:13:10 2026 +0000
stream: avoid per-write allocations in stream/iter writers
Every write() and writeSync() allocated a `{ __proto__: null, context }`
options object for the WebIDL chunk conversion, and for a Uint8Array
chunk a second, six-property one with [AllowShared] and
[AllowResizable]. Both are created in V8 dictionary mode.
Return Uint8Array chunks directly from the WriterChunk converter: with
[AllowShared] and [AllowResizable], the Uint8Array conversion cannot
reject a value isUint8Array() accepts and returns the same object. Use
shared, frozen conversion contexts for chunks, chunk sequences and
write options; the converters only read them to build error messages.
Writing 1e6 16-byte Uint8Array chunks into a push() stream and reading
them back is about 27% faster with writeSync() and with writevSync()
(4 chunks per call). String chunks are unaffected. Behavior and error
messages are unchanged.
Assisted-by: OpenCode
Signed-off-by: James M Snell <jasnell@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/66648
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day>
diff --git a/lib/internal/streams/iter/utils.js b/lib/internal/streams/iter/utils.js
index 0c6e1daaa32..d50ec639ab7 100644
--- a/lib/internal/streams/iter/utils.js
+++ b/lib/internal/streams/iter/utils.js
@@ -412,6 +412,14 @@ function concatBytes(chunks) {
return concatenated;
}
+// Conversion contexts for the per-write paths. The converters only read
+// them (to build error messages), so they are shared rather than allocated
+// on every write.
+const kChunkContext = ObjectFreeze({ __proto__: null, context: 'chunk' });
+const kChunksContext = ObjectFreeze({ __proto__: null, context: 'chunks' });
+const kWriteOptionsContext =
+ ObjectFreeze({ __proto__: null, context: 'options' });
+
/**
* Convert an array of chunks (strings or Uint8Arrays) to a Uint8Array[].
* Always returns a fresh copy of the array.
@@ -419,10 +427,7 @@ function concatBytes(chunks) {
* @returns {Uint8Array[]}
*/
function convertChunks(chunks) {
- chunks = converters.WriterChunkSequence(chunks, {
- __proto__: null,
- context: 'chunks',
- });
+ chunks = converters.WriterChunkSequence(chunks, kChunksContext);
const len = chunks.length;
const result = new Array(len);
for (let i = 0; i < len; i++) {
@@ -437,17 +442,11 @@ function convertChunks(chunks) {
* @returns {AbortSignal|undefined}
*/
function getWriterSignal(options) {
- return converters.WriteOptions(options, {
- __proto__: null,
- context: 'options',
- }).signal;
+ return converters.WriteOptions(options, kWriteOptionsContext).signal;
}
function toWriterUint8Array(chunk) {
- return toUint8Array(converters.WriterChunk(chunk, {
- __proto__: null,
- context: 'chunk',
- }));
+ return toUint8Array(converters.WriterChunk(chunk, kChunkContext));
}
/**
diff --git a/lib/internal/streams/iter/webidl.js b/lib/internal/streams/iter/webidl.js
index ef61abb7794..6ee1afd622a 100644
--- a/lib/internal/streams/iter/webidl.js
+++ b/lib/internal/streams/iter/webidl.js
@@ -27,17 +27,6 @@ function enforceRangeUnsignedLongLong(value, options = { __proto__: null }) {
});
}
-function allowStreamBufferOptions(options) {
- return {
- __proto__: null,
- prefix: options.prefix,
- context: options.context,
- code: options.code,
- allowShared: true,
- allowResizable: true,
- };
-}
-
converters.AbortSignal = baseConverters.AbortSignal;
converters.BackpressurePolicy = createEnumConverter('BackpressurePolicy', [
'strict',
@@ -48,10 +37,11 @@ converters.BackpressurePolicy = createEnumConverter('BackpressurePolicy', [
converters.unsignedLongLong = unsignedLongLong;
converters.enforceRangeUnsignedLongLong = enforceRangeUnsignedLongLong;
converters.WriterChunk = (value, options = { __proto__: null }) => {
- if (isUint8Array(value)) {
- return baseConverters.Uint8Array(
- value, allowStreamBufferOptions(options));
- }
+ // The chunk is a Uint8Array with [AllowShared] and [AllowResizable]. The
+ // Uint8Array conversion cannot reject a value isUint8Array() accepts when
+ // both are allowed, and returns the same object, so skip it: writes are
+ // hot, and the conversion would need its own options object.
+ if (isUint8Array(value)) return value;
return baseConverters.USVString(value, options);
};
converters.WriterChunkSequence = createSequenceConverter(