From be15138681a697529748758b65f1be1a1d346fb5 Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Wed, 24 Jun 2026 06:56:42 -0700 Subject: [PATCH] Add Fantom benchmark for ViewProps cloneProps (#57324) Summary: Adds `ViewProps-benchmark-itest.js` covering three scenarios that exercise the C++ Props construction path: 1. Mount N views with a full prop bag (initial Props construction). 2. Re-render N views with a full prop swap (cloneProps, every prop changes). 3. Re-render N views with a single-prop delta (cloneProps, hot path for typical UI updates). `fantom_flags enableCppPropsIteratorSetter:*` matrix-expands the suite so each scenario runs once with the classic per-field parse and once with the iterator-setter path. This establishes a baseline for upcoming changes that simplify the per-class `flag ? sourceProps.X : convertRawProp(...)` ternaries into two distinct constructors and that skip the now-unused `RawPropsParser::parse()` in the iterator-setter branch. Changelog: [Internal] Differential Revision: D109563268 --- .../__tests__/ViewProps-benchmark-itest.js | 172 ++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 packages/react-native/Libraries/Components/View/__tests__/ViewProps-benchmark-itest.js diff --git a/packages/react-native/Libraries/Components/View/__tests__/ViewProps-benchmark-itest.js b/packages/react-native/Libraries/Components/View/__tests__/ViewProps-benchmark-itest.js new file mode 100644 index 000000000000..0544b9115c2b --- /dev/null +++ b/packages/react-native/Libraries/Components/View/__tests__/ViewProps-benchmark-itest.js @@ -0,0 +1,172 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @fantom_flags enableCppPropsIteratorSetter:* + * @flow strict-local + * @format + */ + +import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment'; + +import * as Fantom from '@react-native/fantom'; +import * as React from 'react'; +import {View} from 'react-native'; + +let root; +let initialTree: React.MixedElement; +let updatedTree: React.MixedElement; + +function buildViewsWithFullPropBag( + count: number, + variant: 'a' | 'b', +): React.MixedElement { + const accent = variant === 'a' ? 'blue' : 'red'; + const offset = variant === 'a' ? 0 : 1; + const children = []; + for (let i = 0; i < count; i++) { + children.push( + , + ); + } + return ( + + {children} + + ); +} + +function buildViewsWithSinglePropDelta( + count: number, + toggle: boolean, +): React.MixedElement { + const children = []; + for (let i = 0; i < count; i++) { + children.push( + , + ); + } + return ( + + {children} + + ); +} + +Fantom.unstable_benchmark + .suite('Props construction') + .test.each( + [100, 1000], + n => `mount ${n.toString()} views with full prop bag`, + () => { + Fantom.runTask(() => root.render(initialTree)); + }, + n => ({ + beforeAll: () => { + initialTree = buildViewsWithFullPropBag(n, 'a'); + }, + beforeEach: () => { + root = Fantom.createRoot(); + }, + afterEach: () => { + root.destroy(); + }, + }), + ) + .test.each( + [100, 1000], + n => `re-render ${n.toString()} views with full prop swap (cloneProps)`, + () => { + Fantom.runTask(() => root.render(updatedTree)); + }, + n => ({ + beforeAll: () => { + initialTree = buildViewsWithFullPropBag(n, 'a'); + updatedTree = buildViewsWithFullPropBag(n, 'b'); + }, + beforeEach: () => { + root = Fantom.createRoot(); + Fantom.runTask(() => root.render(initialTree)); + }, + afterEach: () => { + root.destroy(); + }, + }), + ) + .test.each( + [100, 1000], + n => `re-render ${n.toString()} views with single-prop delta (cloneProps)`, + () => { + Fantom.runTask(() => root.render(updatedTree)); + }, + n => ({ + beforeAll: () => { + initialTree = buildViewsWithSinglePropDelta(n, false); + updatedTree = buildViewsWithSinglePropDelta(n, true); + }, + beforeEach: () => { + root = Fantom.createRoot(); + Fantom.runTask(() => root.render(initialTree)); + }, + afterEach: () => { + root.destroy(); + }, + }), + );