Skip to content

Commit 0fb5beb

Browse files
committed
stream: create the stream/iter share() read reaction once per consumer
share()'s #readAfterPull() created each consumer's afterPull() reaction lazily with `state.afterPull ??= () => {...}`. Because the arrow function captures `this` and `state`, V8 allocates its context on every call of #readAfterPull(), even when afterPull() already exists: about 65 bytes per read that waits for the source. afterPull() is now created by #createAfterPull(), so the context is only allocated when it is. share() fan-out, async source, two consumers, 16-byte chunks: 2065 -> 1944 bytes allocated per chunk. Assisted-by: OpenCode
1 parent 6108877 commit 0fb5beb

1 file changed

Lines changed: 9 additions & 2 deletions

File tree

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,15 @@ class ShareImpl {
408408
// longer pending: clear it from pendingNext, unless a next() has been
409409
// queued behind it, without a reaction of its own.
410410
#readAfterPull(state) {
411-
state.afterPull ??= () => {
411+
// afterPull() is created by a method of its own: if it were created
412+
// here, V8 would allocate the context it captures on every call, even
413+
// when it already exists.
414+
state.afterPull ??= this.#createAfterPull(state);
415+
return PromisePrototypeThen(this.#pullFromSource(false), state.afterPull);
416+
}
417+
418+
#createAfterPull(state) {
419+
return () => {
412420
let result;
413421
try {
414422
result = this.#tryRead(state);
@@ -426,7 +434,6 @@ class ShareImpl {
426434
this.#clearWaitingRead(state);
427435
return result;
428436
};
429-
return PromisePrototypeThen(this.#pullFromSource(false), state.afterPull);
430437
}
431438

432439
#clearWaitingRead(state) {

0 commit comments

Comments
 (0)