stream: fast path single-destination pipe#63592
Conversation
|
Review requested:
|
ronag
left a comment
There was a problem hiding this comment.
This looks to me like it will break stuff. What happens if a data listener is added after pipe? Can you add a test for that.
If a
Added a test in The added test starts with |
2d523a1 to
3184889
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #63592 +/- ##
==========================================
+ Coverage 90.29% 90.31% +0.01%
==========================================
Files 730 730
Lines 234695 234713 +18
Branches 43956 43969 +13
==========================================
+ Hits 211927 211976 +49
+ Misses 14494 14447 -47
- Partials 8274 8290 +16
🚀 New features to boost your workflow:
|
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
Avoid EventEmitter dispatch for the readable pipe data handler when it is the only data listener. Fall back to normal data event emission when additional data listeners are present to preserve listener ordering. Track the pipe data handler lazily so non-piped readable streams do not pay an extra ReadableState property initialization or data listener identity check. Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com> Assisted-by: openai:gpt-5.5
3184889 to
654d196
Compare
|
I attempted to make pipe data fast path state lazy in New Benchmark CI: https://ci.nodejs.org/view/Node.js%20benchmark/job/benchmark-node-micro-benchmarks/1863/ Details confidence improvement accuracy (*) (**) (***)
streams/compose.js n=1000 *** -1.24 % ±0.48% ±0.64% ±0.83%
streams/creation.js kind='duplex' n=50000000 * 0.90 % ±0.77% ±1.03% ±1.35%
streams/creation.js kind='readable' n=50000000 -0.38 % ±1.22% ±1.63% ±2.13%
streams/creation.js kind='transform' n=50000000 0.76 % ±1.47% ±1.97% ±2.59%
streams/creation.js kind='writable' n=50000000 -0.23 % ±0.77% ±1.03% ±1.34%
streams/destroy.js kind='duplex' n=1000000 0.13 % ±0.51% ±0.67% ±0.88%
streams/destroy.js kind='readable' n=1000000 0.39 % ±0.85% ±1.13% ±1.47%
streams/destroy.js kind='transform' n=1000000 0.07 % ±0.39% ±0.52% ±0.68%
streams/destroy.js kind='writable' n=1000000 -0.08 % ±0.63% ±0.83% ±1.08%
streams/finished.js streamType='readable' n=10000000 -0.21 % ±0.67% ±0.89% ±1.16%
streams/finished.js streamType='writable' n=10000000 -0.59 % ±1.16% ±1.55% ±2.04%
streams/iter-creation.js n=100000 type='pair' api='classic' -0.04 % ±4.19% ±5.58% ±7.28%
streams/iter-creation.js n=100000 type='pair' api='iter' 0.41 % ±0.54% ±0.72% ±0.94%
streams/iter-creation.js n=100000 type='pair' api='webstream' 0.27 % ±0.79% ±1.05% ±1.36%
streams/iter-creation.js n=100000 type='readable' api='classic' 0.96 % ±7.79% ±10.37% ±13.49%
streams/iter-creation.js n=100000 type='readable' api='iter' 0.49 % ±2.50% ±3.32% ±4.32%
streams/iter-creation.js n=100000 type='readable' api='webstream' 0.48 % ±0.87% ±1.15% ±1.50%
streams/iter-creation.js n=100000 type='transform' api='classic' -1.11 % ±2.85% ±3.80% ±4.94%
streams/iter-creation.js n=100000 type='transform' api='webstream' 0.26 % ±0.92% ±1.23% ±1.60%
streams/iter-creation.js n=100000 type='writable' api='classic' 1.83 % ±3.55% ±4.73% ±6.16%
streams/iter-creation.js n=100000 type='writable' api='iter' -0.11 % ±2.52% ±3.36% ±4.37%
streams/iter-creation.js n=100000 type='writable' api='webstream' 0.22 % ±1.99% ±2.64% ±3.44%
streams/iter-file-read.js n=5 filesize=1048576 api='classic' 4.34 % ±8.74% ±11.63% ±15.15%
streams/iter-file-read.js n=5 filesize=1048576 api='iter' 1.79 % ±10.99% ±14.62% ±19.04%
streams/iter-file-read.js n=5 filesize=1048576 api='webstream' 1.91 % ±6.05% ±8.06% ±10.49%
streams/iter-file-read.js n=5 filesize=16777216 api='classic' 0.30 % ±6.80% ±9.05% ±11.78%
streams/iter-file-read.js n=5 filesize=16777216 api='iter' -7.58 % ±21.98% ±29.25% ±38.08%
streams/iter-file-read.js n=5 filesize=16777216 api='webstream' 0.27 % ±0.48% ±0.65% ±0.84%
streams/iter-file-read.js n=5 filesize=67108864 api='classic' -0.91 % ±4.89% ±6.50% ±8.46%
streams/iter-file-read.js n=5 filesize=67108864 api='iter' 0.43 % ±15.01% ±19.99% ±26.07%
streams/iter-file-read.js n=5 filesize=67108864 api='webstream' 0.01 % ±0.45% ±0.60% ±0.78%
streams/iter-from-batching.js n=100 chunkSize=16 chunks=256 method='from-sync-writev' 2.28 % ±6.41% ±8.62% ±11.41%
streams/iter-from-batching.js n=100 chunkSize=16 chunks=4096 method='from-sync-writev' -1.06 % ±4.00% ±5.32% ±6.94%
streams/iter-from-batching.js n=1000 chunkSize=16 chunks=16384 method='from-first-batch' 3.36 % ±5.96% ±7.94% ±10.35%
streams/iter-from-batching.js n=1000 chunkSize=16 chunks=256 method='from-first-batch' 0.43 % ±3.77% ±5.01% ±6.53%
streams/iter-from-batching.js n=1000 chunkSize=16 chunks=4096 method='from-first-batch' -2.53 % ±5.43% ±7.23% ±9.41%
streams/iter-throughput-broadcast.js n=5 datasize=1048576 consumers=1 api='classic' -0.47 % ±0.98% ±1.31% ±1.70%
streams/iter-throughput-broadcast.js n=5 datasize=1048576 consumers=1 api='iter' 0.38 % ±0.87% ±1.17% ±1.53%
streams/iter-throughput-broadcast.js n=5 datasize=1048576 consumers=1 api='webstream' * 14.96 % ±14.75% ±19.62% ±25.54%
streams/iter-throughput-broadcast.js n=5 datasize=1048576 consumers=2 api='classic' -1.40 % ±2.66% ±3.57% ±4.71%
streams/iter-throughput-broadcast.js n=5 datasize=1048576 consumers=2 api='iter' -0.73 % ±2.17% ±2.91% ±3.85%
streams/iter-throughput-broadcast.js n=5 datasize=1048576 consumers=2 api='webstream' 1.71 % ±13.00% ±17.30% ±22.52%
streams/iter-throughput-broadcast.js n=5 datasize=1048576 consumers=4 api='classic' * -3.99 % ±3.77% ±5.09% ±6.75%
streams/iter-throughput-broadcast.js n=5 datasize=1048576 consumers=4 api='iter' 18.59 % ±22.30% ±29.74% ±38.84%
streams/iter-throughput-broadcast.js n=5 datasize=1048576 consumers=4 api='webstream' 0.01 % ±8.63% ±11.49% ±14.97%
streams/iter-throughput-broadcast.js n=5 datasize=16777216 consumers=1 api='classic' *** 63.98 % ±7.71% ±10.32% ±13.57%
streams/iter-throughput-broadcast.js n=5 datasize=16777216 consumers=1 api='iter' 2.62 % ±14.61% ±19.44% ±25.31%
streams/iter-throughput-broadcast.js n=5 datasize=16777216 consumers=1 api='webstream' -0.19 % ±8.70% ±11.58% ±15.08%
streams/iter-throughput-broadcast.js n=5 datasize=16777216 consumers=2 api='classic' 1.22 % ±8.10% ±10.77% ±14.02%
streams/iter-throughput-broadcast.js n=5 datasize=16777216 consumers=2 api='iter' 1.45 % ±14.03% ±18.66% ±24.30%
streams/iter-throughput-broadcast.js n=5 datasize=16777216 consumers=2 api='webstream' -4.70 % ±7.73% ±10.30% ±13.42%
streams/iter-throughput-broadcast.js n=5 datasize=16777216 consumers=4 api='classic' -0.47 % ±7.44% ±9.90% ±12.89%
streams/iter-throughput-broadcast.js n=5 datasize=16777216 consumers=4 api='iter' -0.53 % ±11.75% ±15.63% ±20.36%
streams/iter-throughput-broadcast.js n=5 datasize=16777216 consumers=4 api='webstream' 4.26 % ±5.74% ±7.63% ±9.94%
streams/iter-throughput-compression.js n=5 datasize=1048576 api='classic' -11.73 % ±12.91% ±17.18% ±22.36%
streams/iter-throughput-compression.js n=5 datasize=1048576 api='iter' 13.59 % ±14.80% ±19.69% ±25.63%
streams/iter-throughput-compression.js n=5 datasize=1048576 api='webstream' -5.98 % ±10.44% ±13.89% ±18.08%
streams/iter-throughput-compression.js n=5 datasize=16777216 api='classic' -12.01 % ±27.15% ±36.22% ±47.35%
streams/iter-throughput-compression.js n=5 datasize=16777216 api='iter' 9.79 % ±25.84% ±34.39% ±44.78%
streams/iter-throughput-compression.js n=5 datasize=16777216 api='webstream' -12.14 % ±21.65% ±28.81% ±37.49%
streams/iter-throughput-compression.js n=5 datasize=67108864 api='classic' -2.31 % ±19.01% ±25.31% ±32.99%
streams/iter-throughput-compression.js n=5 datasize=67108864 api='iter' -10.09 % ±20.18% ±26.88% ±35.05%
streams/iter-throughput-compression.js n=5 datasize=67108864 api='webstream' -12.56 % ±18.86% ±25.12% ±32.74%
streams/iter-throughput-identity.js n=5 datasize=1048576 api='classic' -2.24 % ±3.62% ±4.87% ±6.43%
streams/iter-throughput-identity.js n=5 datasize=1048576 api='iter-sync' -0.77 % ±1.80% ±2.40% ±3.14%
streams/iter-throughput-identity.js n=5 datasize=1048576 api='iter' -2.19 % ±3.44% ±4.64% ±6.14%
streams/iter-throughput-identity.js n=5 datasize=1048576 api='webstream' 1.42 % ±14.32% ±19.05% ±24.79%
streams/iter-throughput-identity.js n=5 datasize=16777216 api='classic' 6.81 % ±7.90% ±10.53% ±13.75%
streams/iter-throughput-identity.js n=5 datasize=16777216 api='iter-sync' 17.72 % ±38.16% ±50.79% ±66.14%
streams/iter-throughput-identity.js n=5 datasize=16777216 api='iter' -12.37 % ±15.90% ±21.19% ±27.66%
streams/iter-throughput-identity.js n=5 datasize=16777216 api='webstream' -1.38 % ±9.38% ±12.48% ±16.25%
streams/iter-throughput-identity.js n=5 datasize=67108864 api='classic' 6.50 % ±8.21% ±10.92% ±14.22%
streams/iter-throughput-identity.js n=5 datasize=67108864 api='iter-sync' 0.64 % ±10.75% ±14.31% ±18.64%
streams/iter-throughput-identity.js n=5 datasize=67108864 api='iter' 5.40 % ±11.81% ±15.74% ±20.55%
streams/iter-throughput-identity.js n=5 datasize=67108864 api='webstream' -1.88 % ±5.68% ±7.56% ±9.85%
streams/iter-throughput-pipeto.js n=5 datasize=1048576 api='classic' *** -5.22 % ±0.66% ±0.88% ±1.14%
streams/iter-throughput-pipeto.js n=5 datasize=1048576 api='iter-sync-source' -0.81 % ±0.95% ±1.26% ±1.65%
streams/iter-throughput-pipeto.js n=5 datasize=1048576 api='iter-sync' 2.25 % ±2.45% ±3.27% ±4.29%
streams/iter-throughput-pipeto.js n=5 datasize=1048576 api='iter' -0.34 % ±1.13% ±1.50% ±1.95%
streams/iter-throughput-pipeto.js n=5 datasize=1048576 api='webstream' 12.42 % ±14.18% ±18.87% ±24.56%
streams/iter-throughput-pipeto.js n=5 datasize=16777216 api='classic' *** 52.73 % ±10.13% ±13.48% ±17.55%
streams/iter-throughput-pipeto.js n=5 datasize=16777216 api='iter-sync-source' -6.83 % ±22.09% ±29.40% ±38.30%
streams/iter-throughput-pipeto.js n=5 datasize=16777216 api='iter-sync' 26.44 % ±39.86% ±53.07% ±69.15%
streams/iter-throughput-pipeto.js n=5 datasize=16777216 api='iter' -9.74 % ±13.16% ±17.55% ±22.91%
streams/iter-throughput-pipeto.js n=5 datasize=16777216 api='webstream' -5.65 % ±8.70% ±11.61% ±15.17%
streams/iter-throughput-pipeto.js n=5 datasize=67108864 api='classic' 4.16 % ±8.54% ±11.37% ±14.84%
streams/iter-throughput-pipeto.js n=5 datasize=67108864 api='iter-sync-source' -3.07 % ±7.92% ±10.66% ±14.11%
streams/iter-throughput-pipeto.js n=5 datasize=67108864 api='iter-sync' 3.56 % ±8.59% ±11.57% ±15.33%
streams/iter-throughput-pipeto.js n=5 datasize=67108864 api='iter' 3.85 % ±5.40% ±7.23% ±9.51%
streams/iter-throughput-pipeto.js n=5 datasize=67108864 api='webstream' 0.55 % ±6.56% ±8.73% ±11.37%
streams/iter-throughput-share.js n=5 backpressure='block' batches=10000 consumers=2 0.59 % ±1.60% ±2.14% ±2.78%
streams/iter-throughput-share.js n=5 backpressure='block' batches=10000 consumers=32 -0.20 % ±0.38% ±0.50% ±0.65%
streams/iter-throughput-share.js n=5 backpressure='block' batches=10000 consumers=8 ** -1.13 % ±0.70% ±0.93% ±1.21%
streams/iter-throughput-transform.js n=5 datasize=1048576 api='classic' *** -21.36 % ±7.91% ±10.61% ±13.98%
streams/iter-throughput-transform.js n=5 datasize=1048576 api='iter-sync' -0.56 % ±1.28% ±1.71% ±2.22%
streams/iter-throughput-transform.js n=5 datasize=1048576 api='iter' 0.69 % ±16.89% ±22.47% ±29.25%
streams/iter-throughput-transform.js n=5 datasize=1048576 api='webstream' 1.45 % ±8.26% ±10.99% ±14.31%
streams/iter-throughput-transform.js n=5 datasize=16777216 api='classic' *** 7.88 % ±3.76% ±5.01% ±6.54%
streams/iter-throughput-transform.js n=5 datasize=16777216 api='iter-sync' -1.54 % ±6.14% ±8.17% ±10.63%
streams/iter-throughput-transform.js n=5 datasize=16777216 api='iter' 2.59 % ±8.63% ±11.48% ±14.96%
streams/iter-throughput-transform.js n=5 datasize=16777216 api='webstream' -4.19 % ±5.14% ±6.83% ±8.90%
streams/iter-throughput-transform.js n=5 datasize=67108864 api='classic' -3.05 % ±4.07% ±5.44% ±7.12%
streams/iter-throughput-transform.js n=5 datasize=67108864 api='iter-sync' 0.23 % ±3.17% ±4.22% ±5.50%
streams/iter-throughput-transform.js n=5 datasize=67108864 api='iter' 2.28 % ±3.98% ±5.30% ±6.90%
streams/iter-throughput-transform.js n=5 datasize=67108864 api='webstream' 0.01 % ±4.91% ±6.54% ±8.51%
streams/pipe-object-mode.js n=5000000 *** 23.77 % ±1.31% ±1.75% ±2.31%
streams/pipe.js n=5000000 *** 12.56 % ±0.70% ±0.94% ±1.23%
streams/readable-async-iterator.js sync='no' n=100000 -0.48 % ±1.75% ±2.33% ±3.03%
streams/readable-async-iterator.js sync='yes' n=100000 -2.63 % ±4.67% ±6.23% ±8.16%
streams/readable-bigread.js n=1000 1.32 % ±1.85% ±2.46% ±3.20%
streams/readable-bigunevenread.js n=1000 -0.25 % ±0.80% ±1.07% ±1.39%
streams/readable-boundaryread.js type='buffer' n=2000 * -0.66 % ±0.51% ±0.68% ±0.89%
streams/readable-boundaryread.js type='string' n=2000 -0.97 % ±1.16% ±1.54% ±2.01%
streams/readable-from.js type='array' n=10000000 1.38 % ±2.20% ±2.93% ±3.81%
streams/readable-from.js type='async-generator' n=10000000 *** -6.56 % ±1.22% ±1.63% ±2.12%
streams/readable-from.js type='sync-generator-with-async-values' n=10000000 *** -7.26 % ±1.89% ±2.53% ±3.33%
streams/readable-from.js type='sync-generator-with-sync-values' n=10000000 -0.24 % ±0.86% ±1.15% ±1.49%
streams/readable-readall.js n=5000 -0.08 % ±1.08% ±1.44% ±1.87%
streams/readable-uint8array.js kind='encoding' n=1000000 -0.11 % ±1.04% ±1.39% ±1.81%
streams/readable-uint8array.js kind='read' n=1000000 1.09 % ±1.19% ±1.58% ±2.06%
streams/readable-unevenread.js n=1000 -0.59 % ±1.07% ±1.42% ±1.85%
streams/writable-manywrites.js len=1024 callback='no' writev='no' sync='no' n=100000 -1.30 % ±3.81% ±5.07% ±6.61%
streams/writable-manywrites.js len=1024 callback='no' writev='no' sync='yes' n=100000 3.88 % ±11.98% ±15.95% ±20.77%
streams/writable-manywrites.js len=1024 callback='no' writev='yes' sync='no' n=100000 -0.30 % ±5.66% ±7.53% ±9.80%
streams/writable-manywrites.js len=1024 callback='no' writev='yes' sync='yes' n=100000 1.94 % ±5.24% ±7.02% ±9.24%
streams/writable-manywrites.js len=1024 callback='yes' writev='no' sync='no' n=100000 2.75 % ±3.00% ±4.00% ±5.22%
streams/writable-manywrites.js len=1024 callback='yes' writev='no' sync='yes' n=100000 * -8.60 % ±7.30% ±9.72% ±12.66%
streams/writable-manywrites.js len=1024 callback='yes' writev='yes' sync='no' n=100000 4.25 % ±4.72% ±6.28% ±8.17%
streams/writable-manywrites.js len=1024 callback='yes' writev='yes' sync='yes' n=100000 0.67 % ±2.74% ±3.64% ±4.75%
streams/writable-manywrites.js len=32768 callback='no' writev='no' sync='no' n=100000 -2.89 % ±4.01% ±5.33% ±6.94%
streams/writable-manywrites.js len=32768 callback='no' writev='no' sync='yes' n=100000 4.22 % ±10.35% ±13.78% ±17.94%
streams/writable-manywrites.js len=32768 callback='no' writev='yes' sync='no' n=100000 0.23 % ±4.66% ±6.20% ±8.08%
streams/writable-manywrites.js len=32768 callback='no' writev='yes' sync='yes' n=100000 -1.09 % ±3.33% ±4.44% ±5.78%
streams/writable-manywrites.js len=32768 callback='yes' writev='no' sync='no' n=100000 1.22 % ±3.29% ±4.38% ±5.70%
streams/writable-manywrites.js len=32768 callback='yes' writev='no' sync='yes' n=100000 4.61 % ±8.41% ±11.20% ±14.58%
streams/writable-manywrites.js len=32768 callback='yes' writev='yes' sync='no' n=100000 2.25 % ±4.84% ±6.44% ±8.39%
streams/writable-manywrites.js len=32768 callback='yes' writev='yes' sync='yes' n=100000 0.19 % ±1.76% ±2.34% ±3.05%
streams/writable-uint8array.js kind='object-mode' n=50000000 -0.04 % ±0.12% ±0.17% ±0.22%
streams/writable-uint8array.js kind='write' n=50000000 0.02 % ±0.61% ±0.81% ±1.07%
streams/writable-uint8array.js kind='writev' n=50000000 0.04 % ±0.24% ±0.32% ±0.42% |
This comment was marked as outdated.
This comment was marked as outdated.
|
After inlining the non-pipe data emission path in The
The targeted pipe wins are preserved:
Large classic iterator pipeline cases also still improve significantly:
The remaining notable regression is Benchmark CI: https://ci.nodejs.org/view/Node.js%20benchmark/job/benchmark-node-micro-benchmarks/1864/ Outside of these benchmarks, there's an open question if this change will break things in #63592 (review) |
Avoid EventEmitter dispatch for the internal
readable.pipe()data handlerwhen it is the only
datalistener on the source stream.If any additional
datalistener is present, the stream falls back to normaldataevent emission so listener ordering is preserved.Benchmark:
Assisted-by: openai:gpt-5.5