-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Performance tests Fabric
Two complementary test suites measure performance of RN Windows Fabric components:
| Suite | Measures | Timer | Environment |
|---|---|---|---|
| JS Perf Tests | mount / unmount / rerender + scenarios |
React.Profiler (actualDuration) |
Jest (no app needed) |
| Native Perf Tests | Full render pipeline (JS → Fabric → Yoga → Composition → frame) |
performance.mark/measure (QPC ~100 ns) |
Real RNTesterApp-Fabric via UIA |
cd packages/e2e-test-app-fabric
yarn perf # all tests
yarn perf -- --testPathPattern=FlatList # single component
yarn perf:update # update all baselines
yarn perf:update --testPathPattern=TouchableHighlight # update one baseline
yarn perf:create -- --name=ComponentName # scaffold new testCore (9): View, Text, TextInput, Image, Switch, Button, ActivityIndicator, ScrollView, Modal Interactive (3): Pressable, TouchableHighlight, TouchableOpacity List (2): FlatList, SectionList
Each suite tests: mount, unmount, rerender, plus component-specific scenarios (bulk counts, style variants, stress tests).
Core Component Baselines (9 suites)
| Component | Scenario | Baseline (ms) | Max Regression | Min Δ (ms) | Notes |
|---|---|---|---|---|---|
| View | mount | 0 | 10 % | 3 | |
| unmount | 0 | 10 % | 3 | ||
| rerender | 0 | 10 % | 3 | ||
| nested-views-50 | 4 | 15 % | 5 | heavier DOM | |
| nested-views-100 | 7 | 15 % | 5 | heavier DOM | |
| stress-views-500 | 10 | 10 % | 10 | stress gate | |
| with-shadow | 0 | 10 % | 3 | ||
| with-border-radius | 0 | 10 % | 3 | ||
| Text | mount | 0 | 10 % | 3 | |
| unmount | 0 | 10 % | 3 | ||
| rerender | 0 | 10 % | 3 | ||
| long-text-1000 | 0 | 10 % | 3 | ||
| nested-text | 0 | 10 % | 3 | ||
| styled-text | 0 | 10 % | 3 | ||
| multiple-text-100 | 7 | 10 % | 10 | stress gate | |
| Image | mount | 0 | 10 % | 3 | |
| unmount | 0 | 10 % | 3 | ||
| rerender | 0 | 10 % | 3 | ||
| with-resize-mode | 0 | 10 % | 3 | ||
| with-border-radius | 0 | 10 % | 3 | ||
| with-tint-color | 0 | 10 % | 3 | ||
| with-blur-radius | 0 | 10 % | 3 | ||
| with-accessibility | 0 | 10 % | 3 | ||
| multiple-images-10 | 1 | 10 % | 5 | bulk noise | |
| multiple-images-50 | 4 | 15 % | 5 | bulk noise | |
| multiple-images-100 | 8 | 10 % | 10 | stress gate | |
| TextInput | mount | 0 | 10 % | 3 | |
| unmount | 0 | 10 % | 3 | ||
| rerender | 0 | 10 % | 3 | ||
| multiline | 0 | 10 % | 3 | ||
| with-value | 0 | 10 % | 3 | ||
| styled-input | 0 | 10 % | 3 | ||
| multiple-text-inputs-100 | 7 | 10 % | 10 | stress gate | |
| Switch | mount | 0 | 10 % | 3 | |
| unmount | 0 | 10 % | 3 | ||
| rerender | 0 | 10 % | 3 | ||
| disabled | 0.5 | 10 % | 3 | ||
| custom-colors | 0 | 10 % | 3 | ||
| multiple-switches-10 | 1 | 10 % | 5 | bulk noise | |
| multiple-switches-50 | 8 | 15 % | 5 | bulk noise | |
| multiple-switches-100 | 16 | 10 % | 10 | stress gate | |
| Button | mount | 1 | 10 % | 3 | |
| unmount | 0 | 10 % | 3 | ||
| rerender | 1 | 10 % | 3 | ||
| disabled | 1 | 10 % | 3 | ||
| with-color | 1 | 10 % | 3 | ||
| with-accessibility | 1 | 10 % | 3 | ||
| multiple-buttons-10 | 5 | 10 % | 5 | bulk noise | |
| multiple-buttons-50 | 26 | 15 % | 5 | bulk noise | |
| multiple-buttons-100 | 19 | 10 % | 10 | stress gate | |
| ActivityIndicator | mount | 0 | 10 % | 3 | |
| unmount | 0 | 10 % | 3 | ||
| rerender | 0 | 10 % | 3 | ||
| multiple-indicators-10 | 1 | 10 % | 5 | bulk noise | |
| multiple-indicators-50 | 4 | 15 % | 5 | bulk noise | |
| multiple-indicators-100 | 7 | 10 % | 10 | stress gate | |
| ScrollView | mount | 0 | 10 % | 3 | |
| unmount | 0 | 10 % | 3 | ||
| rerender | 0.5 | 10 % | 3 | ||
| with-children-20 | 3 | 10 % | 3 | ||
| with-children-100 | 15 | 15 % | 5 | heavy | |
| horizontal | 3 | 10 % | 3 | ||
| sticky-headers | 3 | 10 % | 3 | ||
| nested-scroll-views | 1 | 10 % | 5 | ||
| with-children-500 | 19 | 10 % | 10 | stress gate | |
| Modal | mount | 0 | 10 % | 3 | |
| unmount | 0 | 10 % | 3 | ||
| rerender | 0.5 | 10 % | 3 | ||
| with-rich-content | 2 | 10 % | 3 |
Interactive Component Baselines (3 suites)
| Component | Scenario | Baseline (ms) | Max Regression | Min Δ (ms) | Notes |
|---|---|---|---|---|---|
| Pressable | mount | 0.5 | 10 % | 3 | |
| unmount | 0 | 10 % | 3 | ||
| rerender | 1 | 10 % | 3 | ||
| nested-pressables | 1 | 10 % | 3 | ||
| multiple-pressables-10 | 3 | 10 % | 5 | bulk noise | |
| multiple-pressables-50 | 15 | 15 % | 5 | bulk noise | |
| multiple-pressables-100 | 12 | 10 % | 10 | stress gate | |
| TouchableOpacity | mount | 1 | 10 % | 3 | |
| rerender | 1.5 | 10 % | 3 | ||
| nested-touchables | 1.5 | 10 % | 3 | ||
| multiple-touchables-10 | 6 | 10 % | 5 | bulk noise | |
| multiple-touchables-50 | 29 | 15 % | 5 | bulk noise | |
| multiple-touchables-100 | 30 | 10 % | 10 | stress gate | |
| TouchableHighlight | mount | 1 | 10 % | 3 | |
| rerender | 0.5 | 10 % | 3 | ||
| nested-touchables | 1 | 10 % | 3 | ||
| multiple-touchables-10 | 2 | 10 % | 5 | bulk noise | |
| multiple-touchables-50 | 12.5 | 15 % | 5 | bulk noise | |
| multiple-touchables-100 | 22.5 | 10 % | 10 | stress gate |
List Component Baselines (2 suites)
| Component | Scenario | Baseline (ms) | Max Regression | Min Δ (ms) | Notes |
|---|---|---|---|---|---|
| FlatList | mount | 4 | 10 % | 3 | |
| unmount | 0 | 10 % | 3 | ||
| rerender | 9 | 10 % | 3 | ||
| with-10-items | 4 | 10 % | 3 | ||
| with-100-items | 5 | 10 % | 5 | ||
| with-500-items | 5 | 15 % | 10 | large list | |
| horizontal | 4.5 | 10 % | 5 | ||
| with-separator | 6 | 10 % | 5 | ||
| with-header-footer | 2 | 10 % | 5 | ||
| with-empty-list | 1 | 10 % | 3 | ||
| with-get-item-layout | 2 | 10 % | 5 | ||
| inverted | 2 | 10 % | 5 | ||
| with-1000-items | 4 | 15 % | 10 | stress gate (virtualized) | |
| with-num-columns | 3 | 10 % | 5 | ||
| SectionList | mount | 5 | 10 % | 3 | |
| unmount | 0 | 10 % | 3 | ||
| rerender | 11 | 10 % | 3 | ||
| 3-sections × 5-items | 5 | 10 % | 5 | ||
| 5-sections × 10-items | 6 | 10 % | 5 | ||
| 10-sections × 20-items | 5.5 | 15 % | 10 | 200 items | |
| 20-sections × 10-items | 5.5 | 15 % | 10 | 200 items | |
| with-section-separator | — | 10 % | 5 | ||
| with-item-separator | — | 10 % | 5 | ||
| with-header-footer | — | 10 % | 5 | ||
| with-section-footer | — | 10 % | 5 | ||
| sticky-section-headers | — | 10 % | 5 | ||
| with-50-sections-20-items | 2 | 15 % | 10 | stress gate (virtualized) | |
| with-empty-list | 0 | 10 % | 3 |
- RNTesterApp-Fabric built and deployed (
Debug|x64orRelease|x64) - WinAppDriver installed (
C:\Program Files (x86)\Windows Application Driver\WinAppDriver.exe) - Metro bundler running
cd packages/e2e-test-app-fabric
# Terminal 1 — start Metro
yarn start
# Terminal 2 — run tests
yarn perf:native # gate mode (compare against baselines)
yarn perf:native:update # save/update baselines
yarn perf:native:ci # CI mode (--ci --forceExit)Each measures native mount — full pipeline from JS reconciliation through Fabric, Yoga layout, to Composition frame commit.
What's Being Measured — Native Render Pipeline
performance.mark('start')
│
├─ JS: setShowTarget(true) ← React state update
├─ React reconciliation (ShadowTree diff)
│
├─ C++: SchedulerDelegate::schedulerDidFinishTransaction()
├─ C++: FabricUIManager::RCTPerformMountInstructions()
│ ├─ ComponentView::updateProps()
│ ├─ ComponentView::updateLayoutMetrics() ← Yoga layout
│ ├─ ComponentView::FinalizeUpdates()
│ │ └─ BorderPrimitive::finalize() ← Composition visuals
│ └─ ComponentView::MountChildComponentView()
│
├─ Visual tree committed to Composition
├─ requestAnimationFrame fires
│
performance.mark('end')
Catches regressions in: prop handling, layout calculation, view creation, border/shadow rendering, visual tree insertion — any change to vnext/ Fabric ComponentView code.
Not measured: GPU composition commit, React reconciler internals (upstream Meta C++), DComposition API overhead.
Timer: performance.mark/measure → QPC (QueryPerformanceCounter), sub-microsecond resolution.
Native Perf Component Baselines (14 components)
| Component | Runs | Warmup | Threshold |
|---|---|---|---|
| View | 15 | 2 | native (15 % max, CV ≤ 0.5) |
| Text | 15 | 2 | native |
| TextInput | 15 | 2 | native |
| Image | 15 | 2 | native |
| Switch | 15 | 2 | native |
| ActivityIndicator | 15 | 2 | native |
| Button | 15 | 2 | native |
| Modal | 15 | 2 | native |
| Pressable | 15 | 2 | native |
| TouchableHighlight | 15 | 2 | native |
| TouchableOpacity | 15 | 2 | native |
| ScrollView | 10 | 2 | native + relaxed CV (≤ 0.6) |
| FlatList | 10 | 2 | native + relaxed (20 %, CV ≤ 0.6) |
| SectionList | 10 | 2 | native + relaxed (20 %, CV ≤ 0.6) |
Every test result passes through this pipeline (in toMatchPerfSnapshot and BaselineComparator):
measured durations[]
│
▼
CV > maxCV? ──yes──▶ SKIP (too noisy to judge) → warn only
│ no
▼
Mann-Whitney U
p ≥ 0.05? ──yes──▶ PASS (not statistically significant)
│ no
▼
% change > maxDurationIncrease%
AND absolute Δ > minAbsoluteDelta ms?
│
yes │ no
▼ ▼
mode? PASS
│
gate ──▶ FAIL CI
track ─▶ WARN only
Both percentage and absolute delta gates must trip simultaneously — prevents a 1 ms → 2 ms jump (100 % but only 1 ms) from blocking CI.
| Preset | Max Regression | Min Δ (ms) | Max Renders | Min Runs | Max CV | Mode |
|---|---|---|---|---|---|---|
| core | 10 % | 3 | 2 | 10 | 0.40 | gate |
| list | 15 % | 5 | 5 | 5 | 0.50 | gate |
| interactive | 20 % | 5 | 10 | 10 | 0.50 | gate |
| native | 15 % | 5 | 1 | 10 | 0.50 | gate |
| community | 25 % | 5 | 15 | 5 | 0.60 | track |
Click to expand
packages/e2e-test-app-fabric/
├── jest.perf.config.js # JS perf Jest config
├── jest.native-perf.config.js # Native perf Jest config
├── jest.perf.setup.ts # Registers toMatchPerfSnapshot matcher
├── test/__perf__/ # JS perf tests
│ ├── core/ # 9 core component tests
│ ├── interactive/ # 3 interactive component tests
│ ├── list/ # 2 list component tests
│ └── */__perf_snapshots__/ # Baseline JSONs
├── test/__native_perf__/ # Native perf tests
│ └── core/
│ ├── View.native-perf-test.ts # All 14 component benchmarks
│ └── __perf_snapshots__/ # Baseline JSONs
└── test/NativePerfHelpers.ts # UIA automation helpers
packages/@react-native-windows/perf-testing/src/
├── base/ComponentPerfTestBase.ts # Abstract base class
├── core/measurePerf.ts # Timing engine (React.Profiler)
├── core/statistics.ts # mean, median, stdDev
├── matchers/toMatchPerfSnapshot.ts # Custom Jest matcher
├── matchers/snapshotManager.ts # Baseline file read/write
├── config/thresholdPresets.ts # Preset definitions
├── ci/BaselineComparator.ts # Regression detection
└── ci/PerfJsonReporter.ts # JSON results for CI
packages/@react-native-windows/tester/src/js/
└── examples-win/NativePerfBenchmark/ # Benchmark page in RNTesterApp
└── NativePerfBenchmarkExample.js