Skip to content

Commit 090f00f

Browse files
committed
stream: snapshot common stream/iter byte views in fewer fields
To detect that a chunk accepted by a writer was resized or detached before it is read, a ByteViewSnapshot of its buffer, byteLength, byteOffset and detached state is taken for every chunk written, and checked when it is read. A non-empty view of a fixed-length, non-shared ArrayBuffer can only change by the buffer being detached, which makes its byteLength 0, as recordChunk() already relies on. Snapshot such views, the common case, as a FixedByteView of the view and its byteLength, which is all that needs to be checked. Writing 16-byte chunks to a push() stream and reading them is about 17% faster with await write() and 37% faster with writeSync(), and piping them about 29% and 51%; 64 KiB chunks are about 7-13% faster. Assisted-by: OpenCode Signed-off-by: James M Snell <jasnell@gmail.com>
1 parent 7b0e7a2 commit 090f00f

1 file changed

Lines changed: 27 additions & 3 deletions

File tree

‎lib/internal/streams/iter/utils.js‎

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,17 @@ function ByteViewSnapshot(value, buffer, sharedBufferView) {
376376
}
377377
ByteViewSnapshot.prototype = ObjectFreeze({ __proto__: null });
378378

379+
// The snapshot of a non-empty view of a fixed-length, non-shared ArrayBuffer,
380+
// the common case. Such a view can only change by the buffer being detached,
381+
// which makes its byteLength 0, so its byteLength is all that needs to be
382+
// recorded. `buffer` is null to tell it from a ByteViewSnapshot.
383+
function FixedByteView(value, byteLength) {
384+
this.value = value;
385+
this.byteLength = byteLength;
386+
this.buffer = null;
387+
}
388+
FixedByteView.prototype = ObjectFreeze({ __proto__: null });
389+
379390
function BatchEntry(views, byteLength) {
380391
this.views = views;
381392
this.byteLength = byteLength;
@@ -399,12 +410,25 @@ PendingWrite.prototype = ObjectFreeze({ __proto__: null });
399410

400411
function snapshotByteView(value) {
401412
const buffer = TypedArrayPrototypeGetBuffer(value);
402-
const sharedBufferView = isSharedArrayBuffer(buffer) ?
403-
new Uint8Array(buffer) : undefined;
404-
return new ByteViewSnapshot(value, buffer, sharedBufferView);
413+
if (isSharedArrayBuffer(buffer)) {
414+
return new ByteViewSnapshot(value, buffer, new Uint8Array(buffer));
415+
}
416+
const byteLength = TypedArrayPrototypeGetByteLength(value);
417+
if (byteLength !== 0 && !ArrayBufferPrototypeGetResizable(buffer)) {
418+
return new FixedByteView(value, byteLength);
419+
}
420+
return new ByteViewSnapshot(value, buffer, undefined);
405421
}
406422

407423
function validateByteView(snapshot) {
424+
if (snapshot.buffer === null) {
425+
const { value } = snapshot;
426+
if (TypedArrayPrototypeGetByteLength(value) !== snapshot.byteLength) {
427+
throw new ERR_INVALID_STATE.TypeError(
428+
'Byte view was resized or detached after being accepted');
429+
}
430+
return value;
431+
}
408432
const {
409433
value,
410434
buffer,

0 commit comments

Comments
 (0)