Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
stream: skip write() checks in flowing pipe
pipe() installs one 'data' listener that calls dest.write() for
every chunk. That repeats encoding, mode, and end checks that stay
the same for a synchronous buffer write.

When that listener is still the only one, hand the Buffer to the
same synchronous write path without those checks. A second
listener, a non-buffer chunk, or a busy writable still goes through
emit('data').

On top of the flowing-read fast path, benchmark/streams/pipe.js is
about 31% faster (20 runs). Object-mode pipe and readable-readall
stay within noise.

Assisted-by: a closed-source coding agent
Signed-off-by: Yagiz Nizipli <yagiz@nizipli.com>
  • Loading branch information
anonrig committed Sep 21, 2026
commit 5c71de5ccae09e8ae45afb594038e882f263ee7c
46 changes: 45 additions & 1 deletion lib/internal/streams/readable.js
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,10 @@ function ReadableState(options, stream, isDuplex) {
this.length = 0;
// Chunk prefetched by flowSync(), kept off the buffer array.
this.fastChunk = null;
// The sole pipe() 'data' listener, when there is exactly one destination.
// flowSync() writes buffers straight to that destination.
this.pipeOnData = null;
this.pipePause = null;
this.pipes = [];

// Should close be emitted on destroy. Defaults to true.
Expand Down Expand Up @@ -985,6 +989,16 @@ Readable.prototype.pipe = function(dest, pipeOpts) {
}

state.pipes.push(dest);
// Only a single pipe destination can skip emit('data') and call write()
// directly. A second destination, or an extra 'data' listener, must go
// through emit so every listener still runs.
if (state.pipes.length === 1) {
state.pipeOnData = ondata;
state.pipePause = pause;
} else {
state.pipeOnData = null;
state.pipePause = null;
}
debug('pipe count=%d opts=%j', state.pipes.length, pipeOpts);

const doEnd = (!pipeOpts || pipeOpts.end !== false) &&
Expand Down Expand Up @@ -1175,6 +1189,8 @@ Readable.prototype.unpipe = function(dest) {
// remove all.
const dests = state.pipes;
state.pipes = [];
state.pipeOnData = null;
state.pipePause = null;
this.pause();

for (let i = 0; i < dests.length; i++)
Expand All @@ -1188,6 +1204,8 @@ Readable.prototype.unpipe = function(dest) {
return this;

state.pipes.splice(index, 1);
state.pipeOnData = null;
state.pipePause = null;
if (state.pipes.length === 0)
this.pause();

Expand Down Expand Up @@ -1380,6 +1398,32 @@ const kFastFlowNeed = kConstructed | kFlowing | kDataListening;
const kFastFlowBlock = kObjectMode | kDecoder | kEnded | kDestroyed |
kErrored | kPaused | kReading | kSync;

let writeKnownBuffer;

// Pipe's only listener is ondata(), which calls dest.write(). Skip emit
// and the general write() checks for a single Buffer in that steady state.
function deliverFlowChunk(stream, state, chunk) {
const pipeOnData = state.pipeOnData;
const events = stream._events;
if (pipeOnData !== null && events !== undefined && events.data === pipeOnData) {
Comment thread
anonrig marked this conversation as resolved.
writeKnownBuffer ??= require('internal/streams/writable').writeKnownBuffer;
const dest = state.pipes[0];
let ret;
try {
ret = writeKnownBuffer(dest, chunk);
} catch (error) {
dest.destroy(error);
return;
}
if (ret === undefined)
stream.emit('data', chunk);
else if (ret === false && state.pipePause !== null)
state.pipePause();
return;
}
stream.emit('data', chunk);
}

// Returns true when this call owned the flowing loop, including any
// fallback to read() after the fast path stops.
function flowSync(stream, state) {
Expand Down Expand Up @@ -1425,7 +1469,7 @@ function flowSync(stream, state) {

if ((state[kState] & (kErrorEmitted | kCloseEmitted)) === 0) {
state[kState] |= kDataEmitted;
stream.emit('data', current);
deliverFlowChunk(stream, state, current);
}

// Nested read() moved fastChunk into the buffer and may have refilled.
Expand Down
37 changes: 37 additions & 0 deletions lib/internal/streams/writable.js
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,43 @@ function writeOrBuffer(stream, state, chunk, encoding, callback) {
return ret && (state[kState] & (kDestroyed | kErrored)) === 0;
}

// Steady state of a flowing pipe into a byte-mode Writable: one Buffer,
// nothing queued, and no user write callback. Returns undefined when the
// caller must use write() instead. Otherwise the same boolean as write().
const kWriteFlowBlock = kObjectMode | kDestroyed | kErrored | kSync |
kEnding | kFinished | kWriting | kCorked | kBuffered | kEnded |
kNeedDrain | kWriteCb | kExpectWriteCb | kBufferProcessing |
kFinalCalled | kPrefinished | kOnFinished | kErrorEmitted;

function writeKnownBuffer(stream, chunk) {
const state = stream._writableState;
if (state == null || state.length !== 0 || !(chunk instanceof Buffer))
return undefined;

const bits = state[kState];
if ((bits & kConstructed) === 0 || (bits & kWriteFlowBlock) !== 0)
return undefined;

const len = chunk.length;
state.pendingcb++;
state.length = len;
state.writelen = len;
state[kState] = bits | kWriting | kSync | kExpectWriteCb;
stream._write(chunk, 'buffer', state.onwrite);
state[kState] &= ~kSync;

const ret = state.length < state.highWaterMark || state.length === 0;
if (!ret)
state[kState] |= kNeedDrain;
return ret && (state[kState] & (kDestroyed | kErrored)) === 0;
}

ObjectDefineProperty(Writable, 'writeKnownBuffer', {
__proto__: null,
value: writeKnownBuffer,
enumerable: false,
});

function doWrite(stream, state, writev, len, chunk, encoding, cb) {
state.writelen = len;
if (cb !== nop) {
Expand Down
Loading