-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
perf(ext/streams): optimize streams #20649
Conversation
This is awesome. Can you see if this test passes w/1000000 writes? It was too slow to pass for me before your PR: https://github.com/denoland/deno/blob/main/cli/tests/unit/streams_test.ts#L252 |
430ee44
to
0b72a92
Compare
Just rebased |
@mmastrac the optimizations are much better than I thought. This PR with:
In
|
I tested this patch with my web-stream serving benchmark and it's ~2% faster vs main. Great work! 57171 -> 58495 for large chunk streams 63183 -> 63183 for small chunk streams |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM but I think @lucacasonato should take a look as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good overall - some comments
return new ReadableStreamDefaultReader(stream); | ||
const reader = new ReadableStreamDefaultReader(_brand); | ||
setUpReadableStreamDefaultReader(reader, stream); | ||
return reader; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a spec fix?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is a performance optimization but also aligns with the spec: https://streams.spec.whatwg.org/#acquire-readable-stream-reader.
- Let reader be a new ReadableStreamDefaultReader.
- Perform ? SetUpReadableStreamDefaultReader(reader, stream).
- Return reader.
ext/web/06_streams.js
Outdated
@@ -4734,6 +4757,32 @@ const asyncIteratorPrototype = ObjectGetPrototypeOf(AsyncGeneratorPrototype); | |||
const _iteratorNext = Symbol("[[iteratorNext]]"); | |||
const _iteratorFinished = Symbol("[[iteratorFinished]]"); | |||
|
|||
class ReadableStreamAsyncIteratorReadRequest { | |||
constructor(reader, promise) { | |||
this.reader = reader; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is vulnerable if a user declares an object getter/setter. Explicitly define the property on the class (maybe private is even faster?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree with making them private, just pushed it. But how is it vulnerable? The class is not exposed and only uses internal fields.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If a user adds a reader
setter/getter to Object.prototype
, this assignment will call that setter rather than creating an object property on this
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To fix it, just declaring the field on the class is enough (class X { reader; constructor() { ... } }
). This causes the locally declared field to shadow the possible setter/getter on the object prototype
return PromisePrototypeThen(promise.promise, (result) => { | ||
reader[_iteratorNext] = null; | ||
if (result.done === true) { | ||
reader[_iteratorFinished] = true; | ||
return { value: undefined, done: true }; | ||
} | ||
return result; | ||
}, (reason) => { | ||
reader[_iteratorNext] = null; | ||
reader[_iteratorFinished] = true; | ||
throw reason; | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where did this code go?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
here
Lines 4766 to 4782 in 6369ad4
chunkSteps(chunk) { | |
this.reader[_iteratorNext] = null; | |
this.promise.resolve({ value: chunk, done: false }); | |
} | |
closeSteps() { | |
this.reader[_iteratorNext] = null; | |
this.reader[_iteratorFinished] = true; | |
readableStreamDefaultReaderRelease(this.reader); | |
this.promise.resolve({ value: undefined, done: true }); | |
} | |
errorSteps(e) { | |
this.reader[_iteratorNext] = null; | |
this.reader[_iteratorFinished] = true; | |
readableStreamDefaultReaderRelease(this.reader); | |
this.promise.reject(e); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a comment for future readers so they know where to change things if the spec changes? Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry @lucacasonato I was OOO, just read this.
AFAIK these state variables are not part of the spec, but needed to pass WPT.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They are defined in this spec: https://webidl.spec.whatwg.org/#es-default-asynchronous-iterator-object
They handle cases like ensuring that calling next()
after it has returned done won't throw
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They are defined in this spec: https://webidl.spec.whatwg.org/#es-default-asynchronous-iterator-object
Thanks, wasn't aware of that.
Added a comment @lucacasonato, feel free to edit it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
Signed-off-by: Luca Casonato <hello@lcas.dev>
This PR introduces several optimizations to streams
Highlights:
ReadableStream
constructor: +20% iter/s.WritableStream
constructor: +50% iter/s.TransformStream
constructor: +30% iter/s.ReadableStream
iterator (both 2 and 20 chunks): +42% and +25% iter/s.ReadableByteStream
iterator (both 2 and 20 chunks): +39% and +20% iter/s.Benchmarks
main
this PR