Skip to content
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

[Fizz] Pipeable Stream Perf #24291

Merged
merged 3 commits into from
Apr 11, 2022
Merged

[Fizz] Pipeable Stream Perf #24291

merged 3 commits into from
Apr 11, 2022

Conversation

gnoff
Copy link
Collaborator

@gnoff gnoff commented Apr 7, 2022

drawing heavily from #24034 this PR should improve performance of renderToPipeableStream.

This PR implements a TypedArray buffer that text is encoded into while writing chunks to the stream. It also adds a fixture which is useful for comparing the baseline rendering performance of renderToPipeableStream and renderToString.

If you want to test out the current implementation simply visit the first commit.
The second commit includes an analog of the buffer which does encoding during the performWork step.
The third commit includes a change to adopt encodeInto which delivers some performance gains by avoiding extra mem copies.

Fixture Performance

The following stats were gathered with npx autocannon -c 5 -d 30 http://localhost:4000/{stream|string} --renderStatusCodes --excludeErrorStats

Before Change
Avg Latency Req per Sec
String 89ms 56rps
Stream 2249ms 2rps
buffering stream chunks
Avg Latency Req per Sec
String 88ms 57rps
Stream 250ms 20rps
encodeInto
Avg Latency Req per Sec
String 88ms 56rps
Stream 146ms 34rps

@gnoff gnoff changed the title Fizz stream perf [Fizz] Pipeable Stream Perf Apr 7, 2022
@sizebot
Copy link

sizebot commented Apr 7, 2022

Comparing: ec52a56...4fd4f93

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name +/- Base Current +/- gzip Base gzip Current gzip
oss-stable/react-dom/cjs/react-dom.production.min.js = 131.23 kB 131.23 kB = 41.95 kB 41.95 kB
oss-experimental/react-dom/cjs/react-dom.production.min.js = 136.49 kB 136.49 kB = 43.53 kB 43.53 kB
facebook-www/ReactDOM-prod.classic.js = 434.46 kB 434.46 kB = 79.76 kB 79.76 kB
facebook-www/ReactDOM-prod.modern.js = 419.45 kB 419.45 kB = 77.40 kB 77.40 kB
facebook-www/ReactDOMForked-prod.classic.js = 434.46 kB 434.46 kB = 79.76 kB 79.76 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.node.development.server.js +5.50% 59.74 kB 63.02 kB +4.42% 15.42 kB 16.10 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.node.development.server.js +5.50% 59.74 kB 63.02 kB +4.42% 15.42 kB 16.10 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.node.development.server.js +5.50% 59.79 kB 63.08 kB +4.42% 15.44 kB 16.12 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.node.production.min.server.js +4.91% 15.70 kB 16.47 kB +4.83% 5.82 kB 6.10 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.node.production.min.server.js +4.91% 15.70 kB 16.47 kB +4.83% 5.82 kB 6.10 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.node.production.min.server.js +4.90% 15.75 kB 16.52 kB +4.83% 5.84 kB 6.13 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name +/- Base Current +/- gzip Base gzip Current gzip
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.node.development.server.js +5.50% 59.74 kB 63.02 kB +4.42% 15.42 kB 16.10 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.node.development.server.js +5.50% 59.74 kB 63.02 kB +4.42% 15.42 kB 16.10 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.node.development.server.js +5.50% 59.79 kB 63.08 kB +4.42% 15.44 kB 16.12 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.node.production.min.server.js +4.91% 15.70 kB 16.47 kB +4.83% 5.82 kB 6.10 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.node.production.min.server.js +4.91% 15.70 kB 16.47 kB +4.83% 5.82 kB 6.10 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.node.production.min.server.js +4.90% 15.75 kB 16.52 kB +4.83% 5.84 kB 6.13 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.production.min.js +1.41% 36.28 kB 36.79 kB +2.50% 12.18 kB 12.49 kB
oss-stable/react-dom/cjs/react-dom-server.node.production.min.js +1.41% 36.28 kB 36.79 kB +2.50% 12.18 kB 12.49 kB
oss-experimental/react-dom/cjs/react-dom-server.node.production.min.js +1.39% 36.74 kB 37.25 kB +2.58% 12.34 kB 12.66 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.development.js +1.39% 228.10 kB 231.27 kB +1.20% 55.42 kB 56.08 kB
oss-stable/react-dom/cjs/react-dom-server.node.development.js +1.39% 228.10 kB 231.27 kB +1.20% 55.42 kB 56.08 kB
oss-experimental/react-dom/cjs/react-dom-server.node.development.js +1.38% 229.52 kB 232.69 kB +1.19% 55.79 kB 56.45 kB
facebook-relay/flight/ReactFlightNativeRelayServer-prod.js +0.31% 21.15 kB 21.21 kB +0.27% 5.23 kB 5.24 kB

Generated by 🚫 dangerJS against 4fd4f93

@gnoff
Copy link
Collaborator Author

gnoff commented Apr 7, 2022

This comment was scratch for capturing some performance timings (code that does not show up in any of the commits currently in this PR). I'll leave it here for clarity's sake but it should be disregarded.

perf stats

| _______| React 18 Stream / encoding eagerly | React 18 Stream / encoding late | React 18 String | React 17 String |
|---:|:---:|:---:|:---:|:---:|
| run X | pw / fcq | pw / fcq | pw / fcq | pw / fcq |
| run 1 | 98 / 28 | 59 / 58| 75 / 9 | |
| run 2 | 99 / 15 | 65 / 70 | 54 / 6 | |
| run 3 | 63 / 28 | 55 / 36 | 26 / 7 | |
| run 4 | 63 / 21 | 27 / 42 | 16 / 2 | |
| run 5 | 71 / 12 | 25 / 54 | 18 / 21 | |
| 10 run avg | 84 / 24 | 41 / 47 | 35 / 11 | |

@gaearon
Copy link
Collaborator

gaearon commented Apr 8, 2022

are docs planned for the bootstrap* options?

Yeah. I'll add it to the list for follow-ups. For now you can inspect the output HTML to see what they do.

@gnoff gnoff force-pushed the fizz-stream-perf branch 4 times, most recently from abe95fc to 39c193d Compare April 8, 2022 21:54
The previous implementation of pipeable streaming (Node) suffered some performance issues brought about by the high chunk counts and innefficiencies with how node streams handle this situation. In particular the use of cork/uncork was meant to alleviate this but these methods do not do anything unless the receiving Writable Stream implements _writev which many won't.

This change adopts the view based buffering techniques previously implemented for the Browser execution context. The main difference is the use of backpressure provided by the writable stream which is not implementable in the other context. Another change to note is the use of standards constructs like TextEncoder and TypedArrays.
encodeInto allows us to write directly to the view buffer that will end up getting streamed instead of encoding into an intermediate buffer and then copying that data.
@gaearon
Copy link
Collaborator

gaearon commented Apr 8, 2022

What do pw and fcq mean?

@gnoff
Copy link
Collaborator Author

gnoff commented Apr 8, 2022

I think I'll delete that comment. it was performance.now timings for performWork and flushCompletedQueues to get a sense of where time was spent depending on where we did encoding. if we do it flushCompletedQueues (which is the current approach of this PR) we delay the work to when we are doing IO which per Seb may be unideal since we could do that work ahead of time. But given the overall speed improvement I think it makes sense to make this tradeoff.

If you look in the main description you will see more broad based network results from autocannon which is more real-world

@@ -437,6 +437,7 @@ const bundles = [
'ReactFlightNativeRelayServerIntegration',
'JSResourceReferenceImpl',
'ReactNativeInternalFeatureFlags',
'util',
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sebmarkbage you commented that this should not be necessary. In practice the build fails without this so I assume ReactServerStreamConfigNode.js is getting pulled in by accident or something changed and now that is the expected file

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The React sync for React Native is currently failing because "util could not be found", I confirmed #24322 fixes it, how can we get it landed?

@gnoff gnoff merged commit fa58002 into facebook:main Apr 11, 2022
@gnoff gnoff deleted the fizz-stream-perf branch April 11, 2022 16:13
rickhanlonii pushed a commit that referenced this pull request Apr 13, 2022
* Add fixture for comparing baseline render perf for renderToString and renderToPipeableStream

Modified from ssr2 and https://github.com/SuperOleg39/react-ssr-perf-test

* Implement buffering in pipeable streams

The previous implementation of pipeable streaming (Node) suffered some performance issues brought about by the high chunk counts and innefficiencies with how node streams handle this situation. In particular the use of cork/uncork was meant to alleviate this but these methods do not do anything unless the receiving Writable Stream implements _writev which many won't.

This change adopts the view based buffering techniques previously implemented for the Browser execution context. The main difference is the use of backpressure provided by the writable stream which is not implementable in the other context. Another change to note is the use of standards constructs like TextEncoder and TypedArrays.

* Implement encodeInto during flushCompletedQueues

encodeInto allows us to write directly to the view buffer that will end up getting streamed instead of encoding into an intermediate buffer and then copying that data.
rickhanlonii pushed a commit that referenced this pull request Apr 14, 2022
* Add fixture for comparing baseline render perf for renderToString and renderToPipeableStream

Modified from ssr2 and https://github.com/SuperOleg39/react-ssr-perf-test

* Implement buffering in pipeable streams

The previous implementation of pipeable streaming (Node) suffered some performance issues brought about by the high chunk counts and innefficiencies with how node streams handle this situation. In particular the use of cork/uncork was meant to alleviate this but these methods do not do anything unless the receiving Writable Stream implements _writev which many won't.

This change adopts the view based buffering techniques previously implemented for the Browser execution context. The main difference is the use of backpressure provided by the writable stream which is not implementable in the other context. Another change to note is the use of standards constructs like TextEncoder and TypedArrays.

* Implement encodeInto during flushCompletedQueues

encodeInto allows us to write directly to the view buffer that will end up getting streamed instead of encoding into an intermediate buffer and then copying that data.
zhengjitf pushed a commit to zhengjitf/react that referenced this pull request Apr 15, 2022
* Add fixture for comparing baseline render perf for renderToString and renderToPipeableStream

Modified from ssr2 and https://github.com/SuperOleg39/react-ssr-perf-test

* Implement buffering in pipeable streams

The previous implementation of pipeable streaming (Node) suffered some performance issues brought about by the high chunk counts and innefficiencies with how node streams handle this situation. In particular the use of cork/uncork was meant to alleviate this but these methods do not do anything unless the receiving Writable Stream implements _writev which many won't.

This change adopts the view based buffering techniques previously implemented for the Browser execution context. The main difference is the use of backpressure provided by the writable stream which is not implementable in the other context. Another change to note is the use of standards constructs like TextEncoder and TypedArrays.

* Implement encodeInto during flushCompletedQueues

encodeInto allows us to write directly to the view buffer that will end up getting streamed instead of encoding into an intermediate buffer and then copying that data.
This was referenced Nov 8, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants