Commit e6c9c8eecdb for nodejs
commit e6c9c8eecdb49cfa4994f9fcc9bb5a7ff3f8d2ea
Author: James M Snell <jasnell@gmail.com>
Date: Sun Oct 4 02:21:50 2026 +0000
stream: construct stream/iter byte view snapshots and batch entries
createBatchEntry() and recordChunk() snapshot every chunk in a
seven-property `{ __proto__: null, ... }` literal, and every batch gets
a `{ __proto__: null, views, byteLength }` record. V8 creates such
literals in dictionary mode, which is expensive for objects created
for every chunk.
Create them with constructors whose prototype is an empty, frozen,
null-prototype object instead, as for IterResult. They have fast
properties and a single shape, and are only used internally.
Writing 1e6 16-byte chunks into a push() stream and reading them back
is about 6x faster with writeSync(), 3.7x faster with writevSync() (4
chunks per call) and 1.7x faster with string chunks.
benchmark/streams/iter-throughput-share-sync.js improves by 67-93%,
iter-throughput-share.js by 4-8%, and iter-throughput-broadcast.js
with 4 consumers by 8%. pipeTo() with small chunks is about 1.5x
faster.
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 d50ec639ab7..915e5340ceb 100644
--- a/lib/internal/streams/iter/utils.js
+++ b/lib/internal/streams/iter/utils.js
@@ -217,23 +217,35 @@ function toUint8Array(chunk) {
return chunk;
}
+// Byte view snapshots and batch entries are created for every chunk and
+// every batch, so like IterResult they are constructed rather than created
+// as `{ __proto__: null, ... }` literals (which are dictionary-mode objects),
+// and their prototype is an empty null-prototype object.
+function ByteViewSnapshot(value, buffer, sharedBufferView) {
+ this.value = value;
+ this.buffer = buffer;
+ this.bufferByteLength = sharedBufferView === undefined ?
+ ArrayBufferPrototypeGetByteLength(buffer) :
+ TypedArrayPrototypeGetByteLength(sharedBufferView);
+ this.byteLength = TypedArrayPrototypeGetByteLength(value);
+ this.byteOffset = TypedArrayPrototypeGetByteOffset(value);
+ this.detached = sharedBufferView === undefined &&
+ ArrayBufferPrototypeGetDetached(buffer);
+ this.sharedBufferView = sharedBufferView;
+}
+ByteViewSnapshot.prototype = ObjectFreeze({ __proto__: null });
+
+function BatchEntry(views, byteLength) {
+ this.views = views;
+ this.byteLength = byteLength;
+}
+BatchEntry.prototype = ObjectFreeze({ __proto__: null });
+
function snapshotByteView(value) {
const buffer = TypedArrayPrototypeGetBuffer(value);
const sharedBufferView = isSharedArrayBuffer(buffer) ?
new Uint8Array(buffer) : undefined;
- return {
- __proto__: null,
- value,
- buffer,
- bufferByteLength: sharedBufferView === undefined ?
- ArrayBufferPrototypeGetByteLength(buffer) :
- TypedArrayPrototypeGetByteLength(sharedBufferView),
- byteLength: TypedArrayPrototypeGetByteLength(value),
- byteOffset: TypedArrayPrototypeGetByteOffset(value),
- detached: sharedBufferView === undefined &&
- ArrayBufferPrototypeGetDetached(buffer),
- sharedBufferView,
- };
+ return new ByteViewSnapshot(value, buffer, sharedBufferView);
}
function validateByteView(snapshot) {
@@ -325,7 +337,7 @@ function createBatchEntry(chunks) {
views[i] = view;
byteLength += view.byteLength;
}
- return { __proto__: null, views, byteLength };
+ return new BatchEntry(views, byteLength);
}
/**
@@ -345,15 +357,14 @@ function splitBatchEntry(entry, limit) {
for (let i = 0; i < views.length; i++) {
const view = views[i];
if (current.length > 0 && byteLength + view.byteLength >= limit) {
- ArrayPrototypePush(entries,
- { __proto__: null, views: current, byteLength });
+ ArrayPrototypePush(entries, new BatchEntry(current, byteLength));
current = [];
byteLength = 0;
}
ArrayPrototypePush(current, view);
byteLength += view.byteLength;
}
- ArrayPrototypePush(entries, { __proto__: null, views: current, byteLength });
+ ArrayPrototypePush(entries, new BatchEntry(current, byteLength));
return entries;
}