stream: improve performance for sync write finishes · nodejs/node@e968e26 · GitHub
Skip to content

Commit e968e26

Browse files
addaleaxtargos
authored andcommitted
stream: improve performance for sync write finishes
Improve performance and reduce memory usage when a writable stream is written to with the same callback (which is the most common case) and when the write operation finishes synchronously (which is also often the case). confidence improvement accuracy (*) (**) (***) streams/writable-manywrites.js sync='no' n=2000000 0.99 % ±3.20% ±4.28% ±5.61% streams/writable-manywrites.js sync='yes' n=2000000 *** 710.69 % ±19.65% ±26.47% ±35.09% Refs: #18013 Refs: #18367 PR-URL: #30710 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 0ba877a commit e968e26

3 files changed

Lines changed: 66 additions & 8 deletions

File tree

benchmark/streams/writable-manywrites.js

Lines changed: 8 additions & 3 deletions

lib/_stream_writable.js

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,10 @@ function WritableState(options, stream, isDuplex) {
142142
// The amount that is being written when _write is called.
143143
this.writelen = 0;
144144

145+
// Storage for data passed to the afterWrite() callback in case of
146+
// synchronous _write() completion.
147+
this.afterWriteTickInfo = null;
148+
145149
this.bufferedRequest = null;
146150
this.lastBufferedRequest = null;
147151

@@ -498,22 +502,41 @@ function onwrite(stream, er) {
498502
}
499503

500504
if (sync) {
501-
process.nextTick(afterWrite, stream, state, cb);
505+
// It is a common case that the callback passed to .write() is always
506+
// the same. In that case, we do not schedule a new nextTick(), but rather
507+
// just increase a counter, to improve performance and avoid memory
508+
// allocations.
509+
if (state.afterWriteTickInfo !== null &&
510+
state.afterWriteTickInfo.cb === cb) {
511+
state.afterWriteTickInfo.count++;
512+
} else {
513+
state.afterWriteTickInfo = { count: 1, cb, stream, state };
514+
process.nextTick(afterWriteTick, state.afterWriteTickInfo);
515+
}
502516
} else {
503-
afterWrite(stream, state, cb);
517+
afterWrite(stream, state, 1, cb);
504518
}
505519
}
506520
}
507521

508-
function afterWrite(stream, state, cb) {
522+
function afterWriteTick({ stream, state, count, cb }) {
523+
state.afterWriteTickInfo = null;
524+
return afterWrite(stream, state, count, cb);
525+
}
526+
527+
function afterWrite(stream, state, count, cb) {
509528
const needDrain = !state.ending && !stream.destroyed && state.length === 0 &&
510529
state.needDrain;
511530
if (needDrain) {
512531
state.needDrain = false;
513532
stream.emit('drain');
514533
}
515-
state.pendingcb--;
516-
cb();
534+
535+
while (count-- > 0) {
536+
state.pendingcb--;
537+
cb();
538+
}
539+
517540
finishMaybe(stream, state);
518541
}
519542

Lines changed: 30 additions & 0 deletions

0 commit comments

Comments
 (0)