Skip to content

Commit b042e7f

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 5ea10a7 commit b042e7f

3 files changed

Lines changed: 66 additions & 8 deletions

File tree

‎benchmark/streams/writable-manywrites.js‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,19 @@ const common = require('../common');
44
constWritable=require('stream').Writable;
55

66
constbench=common.createBenchmark(main,{
7-
n: [2e6]
7+
n: [2e6],
8+
sync: ['yes','no']
89
});
910

10-
functionmain({ n }){
11+
functionmain({ n, sync}){
1112
constb=Buffer.allocUnsafe(1024);
1213
consts=newWritable();
14+
sync=sync==='yes';
1315
s._write=function(chunk,encoding,cb){
14-
cb();
16+
if(sync)
17+
cb();
18+
else
19+
process.nextTick(cb);
1520
};
1621

1722
bench.start();

‎lib/_stream_writable.js‎

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

140+
// Storage for data passed to the afterWrite() callback in case of
141+
// synchronous _write() completion.
142+
this.afterWriteTickInfo=null;
143+
140144
this.bufferedRequest=null;
141145
this.lastBufferedRequest=null;
142146

@@ -483,22 +487,41 @@ function onwrite(stream, er) {
483487
}
484488

485489
if(sync){
486-
process.nextTick(afterWrite,stream,state,cb);
490+
// It is a common case that the callback passed to .write() is always
491+
// the same. In that case, we do not schedule a new nextTick(), but rather
492+
// just increase a counter, to improve performance and avoid memory
493+
// allocations.
494+
if(state.afterWriteTickInfo!==null&&
495+
state.afterWriteTickInfo.cb===cb){
496+
state.afterWriteTickInfo.count++;
497+
}else{
498+
state.afterWriteTickInfo={count: 1, cb, stream, state };
499+
process.nextTick(afterWriteTick,state.afterWriteTickInfo);
500+
}
487501
}else{
488-
afterWrite(stream,state,cb);
502+
afterWrite(stream,state,1,cb);
489503
}
490504
}
491505
}
492506

493-
functionafterWrite(stream,state,cb){
507+
functionafterWriteTick({ stream, state, count, cb }){
508+
state.afterWriteTickInfo=null;
509+
returnafterWrite(stream,state,count,cb);
510+
}
511+
512+
functionafterWrite(stream,state,count,cb){
494513
constneedDrain=!state.ending&&!stream.destroyed&&state.length===0&&
495514
state.needDrain;
496515
if(needDrain){
497516
state.needDrain=false;
498517
stream.emit('drain');
499518
}
500-
state.pendingcb--;
501-
cb();
519+
520+
while(count-->0){
521+
state.pendingcb--;
522+
cb();
523+
}
524+
502525
finishMaybe(stream,state);
503526
}
504527

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
'use strict';
2+
constcommon=require('../common');
3+
const{ Console }=require('console');
4+
const{ Writable }=require('stream');
5+
constasync_hooks=require('async_hooks');
6+
7+
// Make sure that repeated calls to console.log(), and by extension
8+
// stream.write() for the underlying stream, allocate exactly 1 tick object.
9+
// At the time of writing, that is enough to ensure a flat memory profile
10+
// from repeated console.log() calls, rather than having callbacks pile up
11+
// over time, assuming that data can be written synchronously.
12+
// Refs: https://github.com/nodejs/node/issues/18013
13+
// Refs: https://github.com/nodejs/node/issues/18367
14+
15+
constcheckTickCreated=common.mustCall();
16+
17+
async_hooks.createHook({
18+
init(id,type,triggerId,resoure){
19+
if(type==='TickObject')checkTickCreated();
20+
}
21+
}).enable();
22+
23+
constconsole=newConsole(newWritable({
24+
write: common.mustCall((chunk,encoding,cb)=>{
25+
cb();
26+
},100)
27+
}));
28+
29+
for(leti=0;i<100;i++)
30+
console.log(i);

0 commit comments

Comments
 (0)