-
Notifications
You must be signed in to change notification settings - Fork 1k
[labs/ssr] Convert generators to a trampoline pattern #5106
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
Conversation
🦋 Changeset detectedLatest commit: 6a7220c The changes in this PR will be included in the next version bump. This PR includes changesets to release 4 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
📊 Tachometer Benchmark ResultsSummarynop-update
render
update
update-reflect
Resultsthis-change
render
update
update-reflect
this-change, tip-of-tree, previous-release
render
update
nop-update
this-change, tip-of-tree, previous-release
render
update
this-change, tip-of-tree, previous-release
render
update
update-reflect
|
|
The size of lit-html.js and lit-core.min.js are as expected. |
796312d to
0856ede
Compare
|
Benchmark results using @jimsimon's PR: #5103
|
|
I'll patch these changes into Reddit on Monday which should let us validate the performance improvement a bit. This is about 5 times faster than the "strings only" approach I currently have patched in, so we should see a shift in metrics. |
|
I need to push a new changeset file that marks this as a breaking change. I forgot about the ElementRenderer change when I make the first changeset. |
kyubisation
left a comment
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 👍
Thank you very much for your work!
Looks very solid to me.
jimsimon
left a comment
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! Thank you for taking this on!
rictic
left a comment
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.
Woooooooot!
A number of minor issues, only one's really significant (mutating something we probably shouldn't).
This gets rid of generators completely from the SSR code in favor of a thunk/trampoline approach where render functions can return thunks that are called to continue rendering. This keeps our ability to render synchronously or asynchronously, while improving perf.
Benchmarks from #5103 are merged into a separate branch https://github.com/lit/lit/tree/ssr-thunks-benchmarks and show an 18x improvement compared to
main.This approach works by mapping a template's opcodes array into an array of strings or thunks and performing all render state access and mutation in those thunks. Consumers must iterate the array and when they get a thunk, they have to call it and process the return value. The return value can be undefined, a string, another render result, or a promise.
The thunk representation converts recursion and generators into a kind of continuation, and this appears to eliminate a huge amount of overhead.
Like the generator approach, synchronous consumers can throw when they encounter Promises, so we can implement
renderToString()and continue to render synchronously inside ofReact.createElementpatches, etc.Asynchronous consumers can pause evaluation simply by not calling the next thunk yet. They must await the promises return by thunks, and can pause when they receive backpressure signals, etc. from downstream consumers.
The change is mostly backwards compatible.
render()continues to returnRenderResult, which is still an iterable of strings or promises. A new function calledrenderThunked()is added that returns a ThunkedRenderResult, which is an array of strings of thunks.render()callsrenderThunked()` and wraps the result in an iterable that uses a trampoline to read through the result for a small overhead.collectResult(),collectResultSync(), andRenderResultReadablehave been updated to accept bothRenderResults andThunkedRenderResults. Users who are already using those to consumer renders can just change their render call fromrender()torenderThunked()to get the best performance. All existing users will get a signification improvement either way though.Further optimizations are likely possible. One idea is to reduce the number of closures produced by making a Renderer class that keeps the current render state on a stack and produces new values when calling next. Because of this, the
ThunkedRenderResulttype might not be very stable going forward. We may see a few major versions go by as we settle this down - this is labs after all!One breaking change here is to the return type of the ElementRenderer methods. They now directly return a
ThunkedRenderResult. This may be a good stable API to settle on, since if they return iterables of strings, we have to chain iterables (which we did withyield*previously) or change the return type to support iterables of iterables and make nested iterables responsible for kicking off execution of their render whennext()is called - essentially a manual generator.Custom ElementRenderers are more rare than direct SSR
render()users, so it may be worth some churn here to land the drastic performance improvements ASAP, then adjust APIs in future major versions if needed.