fluentui
fluentui copied to clipboard
perf: add stress test app
This PR adds a new app called "Stress Test" that allows Fluent developers to test and compare the performance of v8, v9 and Web Component (WC) Fluent implementations. Today if you want to build a web app using Fluent v8, v9 and WC are the implementations the Fluent team provides and Stress Test exists so we can understand the different performance characteristics of each implementation. This application is still a work in progress but I feel it's reached a point where it should be merged before more work is done with it.
The immediate goal of this application is to establish a performance baseline so we can understand the state of Fluent performance today. Longer term there is much opportunity for Stress Test but the next step is to look at automation and how Stress Test fits into our build pipeline.
Stress Test is a "vanilla" Webpack application because it needs to support React (Fluent v8 and v9) and Web Components (Fluent WC) and for stress testing it needs to treat each implementation "natively". That is, React should work like a typical React app; Web Components should work like a typical WC app.
Initial Test Results
I've compiled the initial test results into a Codesandbox app: https://6u9n59.csb.app/ These tests were run on Windows 11 with an Intel(R) Core(TM) i7-1065G7 CPU @ 1.30GHz 1.50 GHz and 32GB of RAM. Tests were run in Chrome and Firefox.
The test cases are:
mount: measures the amount of time required for the first mount of componentsinject-styles: injects styles into the documentheadand measures how long style recalculation and layout takesprop-update:measures the amount of time a prop update takes to renderadd-node: measures how long it takes to add nodes to the DOMremove-node: measures how long it takes to remove nodes from the DOM
Each of these test cases was run for 100, 250, 500, 750 and 1000 "nodes" (where a "node" is a StressComponent that renders several Fluent components). The number of nodes is the initial number rendered and the number added for add-node. For remove-node the number removed is "initial - 1" (so 100 removes 99 nodes, 250 removes 249, etc).
Other Approaches
Standalone App
A case could be made that Stress Test should live outside the main Fluent repo. I discarded this idea because it's important for it to stay current with the daily changes made to Fluent and ultimately it should be part of the build pipeline.
Vite App
The original implementation of Stress Test used Vite. This is great because it supports both Fluent and WC and provides a fast out-of-the-box experience, however it does not use Webpack. This app needs to use Webpack as some build-time Griffel optimizations are made with Webpack.
Create React App
Stress Test needs to "natively" support WCs and CRA is geared toward React. This isn't to say WCs couldn't work in CRA it just felt like swimming against the tide. Additionally, Stress Test needs to customize the Webpack config for Griffel and this can be challenging in CRA.
Related Issue(s)
Fixes #23422
This pull request is automatically built and testable in CodeSandbox.
To see build info of the built libraries, click here or the icon next to each commit SHA.
Latest deployment of this branch, based on commit 94b46d016f715ea9f2110e5436dc16a18d78ccd8:
| Sandbox | Source |
|---|---|
| @fluentui/react 8 starter | Configuration |
| @fluentui/react-components 9 starter | Configuration |
Asset size changes
Size Auditor did not detect a change in bundle size for any component!
Baseline commit: 0232344dc028b9054c9cd73211b09e491aaa8ab1 (build)
Perf Analysis (@fluentui/react-components)
No significant results to display.
All results
| Scenario | Render type | Master Ticks | PR Ticks | Iterations | Status |
|---|---|---|---|---|---|
| Avatar | mount | 1147 | 1291 | 5000 | |
| Button | mount | 961 | 956 | 5000 | |
| FluentProvider | mount | 1574 | 1569 | 5000 | |
| FluentProviderWithTheme | mount | 634 | 640 | 10 | |
| FluentProviderWithTheme | virtual-rerender | 600 | 598 | 10 | |
| FluentProviderWithTheme | virtual-rerender-with-unmount | 636 | 640 | 10 | |
| MakeStyles | mount | 1915 | 1934 | 50000 | |
| SpinButton | mount | 2505 | 2480 | 5000 |
📊 Bundle size report
Unchanged fixtures
| Package & Exports | Size (minified/GZIP) |
|---|---|
| global-context createContext |
533 B341 B |
| global-context createContextSelector |
554 B348 B |
| priority-overflow createOverflowManager |
2.936 kB1.212 kB |
| react-accordion Accordion (including children components) |
79.562 kB24.121 kB |
| react-alert Alert |
83.79 kB20.841 kB |
| react-avatar Avatar |
48.283 kB13.644 kB |
| react-avatar AvatarGroup |
15.072 kB6.016 kB |
| react-avatar AvatarGroupItem |
68.464 kB19.067 kB |
| react-badge Badge |
22.503 kB7.153 kB |
| react-badge CounterBadge |
23.406 kB7.449 kB |
| react-badge PresenceBadge |
23.947 kB7.022 kB |
| react-button Button |
36.396 kB9.575 kB |
| react-button CompoundButton |
43.469 kB10.812 kB |
| react-button MenuButton |
39.014 kB10.456 kB |
| react-button SplitButton |
46.544 kB11.84 kB |
| react-button ToggleButton |
51.91 kB11.003 kB |
| react-card Card - All |
67.458 kB19.264 kB |
| react-card Card |
63.14 kB18.176 kB |
| react-card CardFooter |
8.461 kB3.555 kB |
| react-card CardHeader |
9.504 kB3.896 kB |
| react-card CardPreview |
8.562 kB3.61 kB |
| react-combobox Combobox (including child components) |
72.762 kB23.757 kB |
| react-combobox Dropdown (including child components) |
71.026 kB23.291 kB |
| react-components react-components: Accordion, Button, FluentProvider, Image, Menu, Popover |
189.031 kB51.967 kB |
| react-components react-components: FluentProvider & webLightTheme |
32.895 kB10.778 kB |
| react-dialog Dialog (including children components) |
85.574 kB25.521 kB |
| react-divider Divider |
16.359 kB5.853 kB |
| react-image Image |
10.68 kB4.215 kB |
| react-input Input |
23.554 kB7.644 kB |
| react-label Label |
9.238 kB3.815 kB |
| react-link Link |
12.231 kB4.925 kB |
| react-menu Menu (including children components) |
115.929 kB35.379 kB |
| react-menu Menu (including selectable components) |
119.128 kB35.874 kB |
| react-overflow hooks only |
10.898 kB4.174 kB |
| react-popover Popover |
103.05 kB31.561 kB |
| react-portal Portal |
10.576 kB3.875 kB |
| react-positioning usePositioning |
19.7 kB7.404 kB |
| react-provider FluentProvider |
15.655 kB5.835 kB |
| react-radio Radio |
36.238 kB12 kB |
| react-radio RadioGroup |
14.361 kB5.728 kB |
| react-select Select |
20.746 kB7.299 kB |
| react-slider Slider |
32.07 kB10.033 kB |
| react-spinbutton SpinButton |
43.899 kB12.362 kB |
| react-spinner Spinner |
19.848 kB6.384 kB |
| react-switch Switch |
32.562 kB10.253 kB |
| react-text Text - Default |
11.682 kB4.561 kB |
| react-text Text - Wrappers |
14.992 kB4.995 kB |
| react-textarea Textarea |
23.674 kB7.83 kB |
| react-theme Single theme token import |
69 B89 B |
| react-theme Teams: all themes |
29.224 kB6.255 kB |
| react-theme Teams: Light theme |
17.088 kB4.89 kB |
| react-tooltip Tooltip |
41.504 kB14.622 kB |
| react-utilities SSRProvider |
180 B159 B |
Perf Analysis (@fluentui/react-northstar)
Perf tests with no regressions
| Scenario | Current PR Ticks | Baseline Ticks | Ratio |
|---|---|---|---|
| RefMinimalPerf.default | 187 | 160 | 1.17:1 |
| AlertMinimalPerf.default | 215 | 188 | 1.14:1 |
| RadioGroupMinimalPerf.default | 357 | 317 | 1.13:1 |
| AvatarMinimalPerf.default | 163 | 150 | 1.09:1 |
| PortalMinimalPerf.default | 150 | 137 | 1.09:1 |
| IconMinimalPerf.default | 499 | 457 | 1.09:1 |
| TextAreaMinimalPerf.default | 413 | 382 | 1.08:1 |
| ToolbarMinimalPerf.default | 774 | 716 | 1.08:1 |
| ChatDuplicateMessagesPerf.default | 240 | 227 | 1.06:1 |
| DialogMinimalPerf.default | 645 | 608 | 1.06:1 |
| MenuMinimalPerf.default | 715 | 677 | 1.06:1 |
| ChatWithPopoverPerf.default | 273 | 261 | 1.05:1 |
| HeaderSlotsPerf.default | 643 | 615 | 1.05:1 |
| MenuButtonMinimalPerf.default | 1402 | 1337 | 1.05:1 |
| ReactionMinimalPerf.default | 313 | 299 | 1.05:1 |
| CheckboxMinimalPerf.default | 2251 | 2168 | 1.04:1 |
| GridMinimalPerf.default | 280 | 270 | 1.04:1 |
| ListCommonPerf.default | 513 | 493 | 1.04:1 |
| SegmentMinimalPerf.default | 285 | 275 | 1.04:1 |
| CustomToolbarPrototype.default | 2267 | 2171 | 1.04:1 |
| DropdownManyItemsPerf.default | 560 | 546 | 1.03:1 |
| SkeletonMinimalPerf.default | 281 | 272 | 1.03:1 |
| TableMinimalPerf.default | 339 | 328 | 1.03:1 |
| DropdownMinimalPerf.default | 2616 | 2573 | 1.02:1 |
| HeaderMinimalPerf.default | 282 | 277 | 1.02:1 |
| ProviderMinimalPerf.default | 338 | 331 | 1.02:1 |
| SliderMinimalPerf.default | 1400 | 1378 | 1.02:1 |
| TreeWith60ListItems.default | 132 | 129 | 1.02:1 |
| FormMinimalPerf.default | 332 | 329 | 1.01:1 |
| InputMinimalPerf.default | 1084 | 1072 | 1.01:1 |
| LabelMinimalPerf.default | 315 | 311 | 1.01:1 |
| ListMinimalPerf.default | 422 | 416 | 1.01:1 |
| SplitButtonMinimalPerf.default | 3618 | 3596 | 1.01:1 |
| TableManyItemsPerf.default | 1533 | 1512 | 1.01:1 |
| TooltipMinimalPerf.default | 923 | 913 | 1.01:1 |
| EmbedMinimalPerf.default | 3404 | 3421 | 1:1 |
| FlexMinimalPerf.default | 234 | 234 | 1:1 |
| LoaderMinimalPerf.default | 573 | 571 | 1:1 |
| PopupMinimalPerf.default | 517 | 516 | 1:1 |
| ProviderMergeThemesPerf.default | 1053 | 1050 | 1:1 |
| StatusMinimalPerf.default | 553 | 553 | 1:1 |
| ButtonMinimalPerf.default | 124 | 125 | 0.99:1 |
| ChatMinimalPerf.default | 582 | 589 | 0.99:1 |
| ImageMinimalPerf.default | 304 | 308 | 0.99:1 |
| LayoutMinimalPerf.default | 285 | 288 | 0.99:1 |
| ListNestedPerf.default | 452 | 457 | 0.99:1 |
| ButtonSlotsPerf.default | 387 | 394 | 0.98:1 |
| ItemLayoutMinimalPerf.default | 969 | 992 | 0.98:1 |
| TreeMinimalPerf.default | 647 | 661 | 0.98:1 |
| AttachmentSlotsPerf.default | 881 | 910 | 0.97:1 |
| ButtonOverridesMissPerf.default | 1207 | 1238 | 0.97:1 |
| DividerMinimalPerf.default | 284 | 293 | 0.97:1 |
| ListWith60ListItems.default | 492 | 511 | 0.96:1 |
| RosterPerf.default | 897 | 932 | 0.96:1 |
| TextMinimalPerf.default | 260 | 272 | 0.96:1 |
| DatepickerMinimalPerf.default | 4394 | 4614 | 0.95:1 |
| VideoMinimalPerf.default | 508 | 533 | 0.95:1 |
| CardMinimalPerf.default | 403 | 438 | 0.92:1 |
| AccordionMinimalPerf.default | 107 | 117 | 0.91:1 |
| AttachmentMinimalPerf.default | 106 | 116 | 0.91:1 |
| AnimationMinimalPerf.default | 382 | 426 | 0.9:1 |
| BoxMinimalPerf.default | 245 | 272 | 0.9:1 |
| CarouselMinimalPerf.default | 345 | 385 | 0.9:1 |
Perf Analysis (@fluentui/react)
No significant results to display.
All results
| Scenario | Render type | Master Ticks | PR Ticks | Iterations | Status |
|---|---|---|---|---|---|
| BaseButton | mount | 787 | 804 | 5000 | |
| Breadcrumb | mount | 2376 | 2338 | 1000 | |
| Checkbox | mount | 2250 | 2241 | 5000 | |
| CheckboxBase | mount | 1970 | 1976 | 5000 | |
| ChoiceGroup | mount | 4071 | 4097 | 5000 | |
| ComboBox | mount | 839 | 843 | 1000 | |
| CommandBar | mount | 9116 | 9079 | 1000 | |
| ContextualMenu | mount | 10252 | 10222 | 1000 | |
| DefaultButton | mount | 976 | 973 | 5000 | |
| DetailsRow | mount | 3298 | 3375 | 5000 | |
| DetailsRowFast | mount | 3368 | 3323 | 5000 | |
| DetailsRowNoStyles | mount | 3153 | 3145 | 5000 | |
| Dialog | mount | 2516 | 2504 | 1000 | |
| DocumentCardTitle | mount | 166 | 143 | 1000 | |
| Dropdown | mount | 2837 | 2839 | 5000 | |
| FocusTrapZone | mount | 1618 | 1654 | 5000 | |
| FocusZone | mount | 1570 | 1576 | 5000 | |
| IconButton | mount | 1507 | 1546 | 5000 | |
| Label | mount | 297 | 313 | 5000 | |
| Layer | mount | 2876 | 2859 | 5000 | |
| Link | mount | 410 | 400 | 5000 | |
| MenuButton | mount | 1290 | 1281 | 5000 | |
| MessageBar | mount | 1800 | 1896 | 5000 | |
| Nav | mount | 2829 | 2862 | 1000 | |
| OverflowSet | mount | 956 | 971 | 5000 | |
| Panel | mount | 1927 | 1951 | 1000 | |
| Persona | mount | 849 | 865 | 1000 | |
| Pivot | mount | 1247 | 1269 | 1000 | |
| PrimaryButton | mount | 1149 | 1132 | 5000 | |
| Rating | mount | 6699 | 6745 | 5000 | |
| SearchBox | mount | 1120 | 1117 | 5000 | |
| Shimmer | mount | 2162 | 2141 | 5000 | |
| Slider | mount | 1667 | 1673 | 5000 | |
| SpinButton | mount | 4625 | 4376 | 5000 | |
| Spinner | mount | 373 | 366 | 5000 | |
| SplitButton | mount | 2768 | 2750 | 5000 | |
| Stack | mount | 456 | 445 | 5000 | |
| StackWithIntrinsicChildren | mount | 1969 | 1978 | 5000 | |
| StackWithTextChildren | mount | 4508 | 4520 | 5000 | |
| SwatchColorPicker | mount | 10042 | 10091 | 5000 | |
| TagPicker | mount | 2389 | 2332 | 5000 | |
| TeachingBubble | mount | 85365 | 84116 | 5000 | |
| Text | mount | 376 | 354 | 5000 | |
| TextField | mount | 1201 | 1208 | 5000 | |
| ThemeProvider | mount | 1113 | 1100 | 5000 | |
| ThemeProvider | virtual-rerender | 631 | 635 | 5000 | |
| ThemeProvider | virtual-rerender-with-unmount | 1617 | 1634 | 5000 | |
| Toggle | mount | 697 | 700 | 5000 | |
| buttonNative | mount | 109 | 113 | 5000 |
dat pipeline is paaaaassing 🤝😍