stream: decouple transform backpressure changes - #65143
Open
mcollina wants to merge 4 commits into
Open
Conversation
The highWaterMark values were passed as properties of the underlying source and sink dictionaries, where they are ignored: a queuing strategy's highWaterMark is read from the constructors' second argument. Every configuration therefore measured the identical workload at the default highWaterMark of 1, which also explains the historically high run-to-run variance of this benchmark family. Pass the strategies as the constructors' second argument and cover the default (1) alongside buffered (1024, 4096) configurations. Signed-off-by: Matteo Collina <hello@matteocollina.com>
Three related reductions on the per-chunk paths: Wrap user sink.write and source.pull callbacks without coercing their result into a promise. When the callback returns a non-thenable (the common synchronous case), fulfillment is guaranteed and no then() lookup is observable, so the fulfilled reaction is enqueued through a single shared resolved promise at the exact microtask position the coerced promise's reaction would have had, skipping the implicit async-wrapper promise per chunk. Thenable results go through PromiseResolve(), which matches the spec's "a promise resolved with" conversion (identity for native promises). Park pipeTo's pump on backpressure by installing a record that duck-types the writer's lazily-materialized [[readyPromise]] record and whose resolve function is the pump continuation itself. Backpressure clearing then resumes the pump directly instead of materializing a fresh promise record plus reaction per flip, and the pump no longer schedules a microtask per batch. writableStreamUpdateBackpressure publishes the new backpressure state before resolving the ready record so the pump observes the updated value. Replace queueMicrotask() on the pipeTo and tee chunk-forwarding paths with a reaction on the shared resolved promise, which enqueues the continuation at the same position without the per-call scheduling overhead. pipe-to improves by 8-14% across all benchmark configurations, with readable-read and tee also improving in spot runs. Signed-off-by: Matteo Collina <hello@matteocollina.com>
The start, pull, and write non-op algorithms are all raw callbacks with an identical empty body now, so a single shared nonOpCallback replaces nonOpStart, nonOpPull, and nonOpWrite. Signed-off-by: Matteo Collina <hello@matteocollina.com>
The spec's [[backpressureChangePromise]] is only ever observed by the transform source pull algorithm (settles when backpressure next becomes true) and by a sink write arriving under backpressure (settles when it next becomes false), both internal. Replace the promise record with direct continuation delivery: a parked pull is completed by enqueueing the readable controller's pull-fulfilled step on the shared resolved promise, and a parked write by a cached per-stream continuation that resolves the sink promise with the perform-transform promise, so adoption reproduces the previous derived-chain settle depth exactly. Each delivery lands at the same microtask position as the old record's reaction. transformStreamDefaultControllerPerformTransform now mirrors the reference implementation's promiseCall().then(undefined, rejection steps) directly instead of running an async wrapper pair per chunk: the transformer.transform callback is wrapped raw, a non-thenable result reuses the shared resolved promise, and a synchronous throw is delivered through a rejected promise, keeping the error timing inside a reaction. A pipe-through benchmark is added since the suite had no transform-throughput row. pipeThrough passthrough improves by ~8% and a writer-driven transform loop by ~14%; the pipe-to and read families are unchanged. Signed-off-by: Matteo Collina <hello@matteocollina.com>
Collaborator
|
Review requested:
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #65143 +/- ##
==========================================
+ Coverage 90.30% 90.33% +0.02%
==========================================
Files 759 759
Lines 248294 248489 +195
Branches 46860 46881 +21
==========================================
+ Hits 224220 224465 +245
+ Misses 15516 15468 -48
+ Partials 8558 8556 -2
🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Thirteenth round of pure-JS webstreams optimizations. Stacked on #65138 — the first three commits are that PR; please review only the last commit (
stream: decouple transform backpressure changes).The transform stream's
[[backpressureChangePromise]]is observed only internally: by the source pull algorithm (settles when backpressure next becomes true) and by a sink write arriving under backpressure (settles when it next becomes false). On thepipeThroughpassthrough shape both flips are observed on every chunk, so the record plus its reactions and theperformTransformasync-wrapper pair accounted for roughly 10% of the profile.kParkedAlgorithmResult) and the next backpressure→true flip enqueues the readable controller's cached pull-fulfilled step on the shared resolved promise — the exact microtask position of the old record's reaction. A parked write stores the chunk plus one promise record; the false-flip runs a cached per-stream continuation that resolves it with the perform-transform promise, and adoption reproduces the previous derived-chain settle depth bit-for-bit. Erroring/cancel paths deliver through the same flip (transformStreamUnblockWrite), rejecting inside the continuation microtask like the old reaction-throw did.performTransform.transformStreamDefaultControllerPerformTransformnow mirrors the reference implementation'spromiseCall(transformAlgorithm, …).then(undefined, rejectionSteps)directly: thetransformer.transformcallback is wrapped raw, a non-thenable result reuses the shared resolved promise, and a synchronous throw is delivered through a rejected promise so the stream is still errored inside a reaction, not synchronously.pipe-throughbenchmark, since the suite had no transform-throughput row.Benchmark results (30 runs, vs the #65138 head):
A writer-driven
write()/read()transform loop improves ~14% in spot runs.creation,js_transfer,pipe-to, and the read families are unchanged (all n.s.; the machine's ambient load makes the confidence intervals wide — direction was consistent across interleaved spot runs).Verified with the WPT streams/compression/encoding suites, the full parallel webstream/whatwg set, and a transform-specific ordering stress (writer-driven backpressure parking with microtask-depth probes, reentrant reads inside
transform(),terminate()mid-stream, rejected/throwing transforms, reader cancel with a parked write, chainedpipeThrough) whose event log is byte-identical to the #65138 head.