Skip to content

[Fizz] Pipeable Stream Perf - #24291

Merged
gnoff merged 3 commits into
react:mainfrom
gnoff:fizz-stream-perf
Apr 11, 2022
Merged

[Fizz] Pipeable Stream Perf#24291
gnoff merged 3 commits into
react:mainfrom
gnoff:fizz-stream-perf

Conversation

@gnoff

@gnoffgnoff commented Apr 7, 2022

Copy link
Copy Markdown
Collaborator

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 LatencyReq per Sec
String89ms56rps
Stream2249ms2rps
buffering stream chunks
Avg LatencyReq per Sec
String88ms57rps
Stream250ms20rps
encodeInto
Avg LatencyReq per Sec
String88ms56rps
Stream146ms34rps

@gnoffgnoff changed the title Fizz stream perf[Fizz] Pipeable Stream PerfApr 7, 2022
@sizebot

sizebot commented Apr 7, 2022

Copy link
Copy Markdown

Comparing: ec52a56...4fd4f93

Critical size changes

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

Name+/-BaseCurrent+/- gzipBase gzipCurrent gzip
oss-stable/react-dom/cjs/react-dom.production.min.js=131.23 kB131.23 kB=41.95 kB41.95 kB
oss-experimental/react-dom/cjs/react-dom.production.min.js=136.49 kB136.49 kB=43.53 kB43.53 kB
facebook-www/ReactDOM-prod.classic.js=434.46 kB434.46 kB=79.76 kB79.76 kB
facebook-www/ReactDOM-prod.modern.js=419.45 kB419.45 kB=77.40 kB77.40 kB
facebook-www/ReactDOMForked-prod.classic.js=434.46 kB434.46 kB=79.76 kB79.76 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.node.development.server.js+5.50%59.74 kB63.02 kB+4.42%15.42 kB16.10 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.node.development.server.js+5.50%59.74 kB63.02 kB+4.42%15.42 kB16.10 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.node.development.server.js+5.50%59.79 kB63.08 kB+4.42%15.44 kB16.12 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.node.production.min.server.js+4.91%15.70 kB16.47 kB+4.83%5.82 kB6.10 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.node.production.min.server.js+4.91%15.70 kB16.47 kB+4.83%5.82 kB6.10 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.node.production.min.server.js+4.90%15.75 kB16.52 kB+4.83%5.84 kB6.13 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name+/-BaseCurrent+/- gzipBase gzipCurrent gzip
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.node.development.server.js+5.50%59.74 kB63.02 kB+4.42%15.42 kB16.10 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.node.development.server.js+5.50%59.74 kB63.02 kB+4.42%15.42 kB16.10 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.node.development.server.js+5.50%59.79 kB63.08 kB+4.42%15.44 kB16.12 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.node.production.min.server.js+4.91%15.70 kB16.47 kB+4.83%5.82 kB6.10 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.node.production.min.server.js+4.91%15.70 kB16.47 kB+4.83%5.82 kB6.10 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.node.production.min.server.js+4.90%15.75 kB16.52 kB+4.83%5.84 kB6.13 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.production.min.js+1.41%36.28 kB36.79 kB+2.50%12.18 kB12.49 kB
oss-stable/react-dom/cjs/react-dom-server.node.production.min.js+1.41%36.28 kB36.79 kB+2.50%12.18 kB12.49 kB
oss-experimental/react-dom/cjs/react-dom-server.node.production.min.js+1.39%36.74 kB37.25 kB+2.58%12.34 kB12.66 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.development.js+1.39%228.10 kB231.27 kB+1.20%55.42 kB56.08 kB
oss-stable/react-dom/cjs/react-dom-server.node.development.js+1.39%228.10 kB231.27 kB+1.20%55.42 kB56.08 kB
oss-experimental/react-dom/cjs/react-dom-server.node.development.js+1.38%229.52 kB232.69 kB+1.19%55.79 kB56.45 kB
facebook-relay/flight/ReactFlightNativeRelayServer-prod.js+0.31%21.15 kB21.21 kB+0.27%5.23 kB5.24 kB

Generated by 🚫 dangerJS against 4fd4f93

Comment threadpackages/react-dom/src/__tests__/ReactDOMFizzServer-test.js Outdated
@gnoff
gnoffforce-pushed the fizz-stream-perf branch from 20123a4 to bec37d2CompareApril 7, 2022 15:41
Comment threadpackages/react-server/src/ReactServerStreamConfigNode.js
@gnoff

gnoff commented Apr 7, 2022

Copy link
Copy Markdown
CollaboratorAuthor

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 | |

@gnoff
gnoffforce-pushed the fizz-stream-perf branch from 5886bfc to f96541eCompareApril 8, 2022 21:43
@gaearon

Copy link
Copy Markdown
Collaborator

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
gnoffforce-pushed the fizz-stream-perf branch 4 times, most recently from abe95fc to 39c193dCompareApril 8, 2022 21:54
@gnoff
gnoffforce-pushed the fizz-stream-perf branch from 39c193d to d036841CompareApril 8, 2022 22:12
gnoff added 2 commits April 8, 2022 15:34
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.
@gnoff
gnoffforce-pushed the fizz-stream-perf branch from d036841 to 4fd4f93CompareApril 8, 2022 22:34
@gaearon

Copy link
Copy Markdown
Collaborator

What do pw and fcq mean?

@gnoff

gnoff commented Apr 8, 2022

Copy link
Copy Markdown
CollaboratorAuthor

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

'ReactFlightNativeRelayServerIntegration',
'JSResourceReferenceImpl',
'ReactNativeInternalFeatureFlags',
'util',

Copy link
Copy Markdown
CollaboratorAuthor

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
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Contributor

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 react:mainApr 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
mrizwanashiq pushed a commit to mrizwanashiq/react that referenced this pull request Jun 25, 2026
* 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.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@gnoff@sizebot@gaearon@sebmarkbage@rickhanlonii@acdlite@facebook-github-bot