Add benchmark to compare rendering times for View and ViewNativeComponent (#53248)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53248

Changelog: [internal]

This adds a new benchmark that compares the rendering time (only rendering, not committing, mounting, effects, etc.) of `<View>` and `<ViewNativeComponent>`.

Baseline:

| (index) | Task name                                | Latency avg (ns)  | Latency med (ns)   | Throughput avg (ops/s) | Throughput med (ops/s) | Samples |
| ------- | ---------------------------------------- | ----------------- | ------------------ | ---------------------- | ---------------------- | ------- |
| 0       | 'render 100 views (Noop)'                | '333036 ± 0.39%'  | '328452 ± 2393.0'  | '3019 ± 0.18%'         | '3045 ± 22'            | 3003    |
| 1       | 'render 100 views (ViewNativeComponent)' | '1335974 ± 3.45%' | '1228468 ± 7541.5' | '797 ± 0.71%'          | '814 ± 5'              | 1000    |
| 2       | 'render 100 views (View)'                | '2296988 ± 1.60%' | '2170821 ± 12374'  | '449 ± 0.74%'          | '461 ± 3'              | 1000    |

This shows that **`<View>` currently has an overhead of 75% in rendering time**.

I've also tested a modification of `View` such as:

```
component View(...props: ViewProps) {
  return {
    // This tag allows us to uniquely identify this as a React Element
    $$typeof: REACT_ELEMENT_TYPE,
    // Built-in properties that belong on the element
    type: ViewNativeComponent,
    key: undefined,
    // $FlowExpectedError[prop-missing]
    ref: props.ref,
    props,
  };
}
```

This makes `View` basically a no-op component, and the benchmark after this looks like:

| (index) | Task name                                | Latency avg (ns)  | Latency med (ns)  | Throughput avg (ops/s) | Throughput med (ops/s) | Samples |
| ------- | ---------------------------------------- | ----------------- | ----------------- | ---------------------- | ---------------------- | ------- |
| 0       | 'render 100 views (View)'                | '1743010 ± 2.25%' | '1630816 ± 10616' | '600 ± 0.74%'          | '613 ± 4'              | 1000    |
| 1       | 'render 100 views (ViewNativeComponent)' | '1370699 ± 4.04%' | '1242284 ± 14172' | '789 ± 0.74%'          | '805 ± 9'              | 1000    |

This shows that `View`, just for existing as a wrapper component, has an overhead of 31% in rendering time, which means that **the opportunities to reduce the overhead beyond what we already did are limited**.

Reviewed By: rshest

Differential Revision: D80169514

fbshipit-source-id: aa2a1fc3f9d0ee3a60c03dd32555802fa7265251
This commit is contained in:
Rubén Norte authored and Facebook GitHub Bot committed 2025-08-13 10:14:21 -07:00
1 parent e8bf982bac
commit d009a02c6c
2 files changed
+106

No files matched your search

@@ -0,0 +1,60 @@
/**
* 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.
*
* @flow strict-local
* @format
*/
import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment';
import measureRenderTime from '../../../../src/private/__tests__/utilities/measureRenderTime';
import ViewNativeComponent from '../ViewNativeComponent';
import * as Fantom from '@react-native/fantom';
import * as React from 'react';
import {View} from 'react-native';
let root;
let testViews: React.MixedElement;
const NUMBER_OF_VIEWS = 100;
const NUMBER_OF_ITERATIONS = 1000;
component Noop(children: React.Node, style?: mixed) {
return children;
}
Noop.displayName = 'Noop';
Fantom.unstable_benchmark
.suite('View vs. ViewNativeComponent', {minIterations: NUMBER_OF_ITERATIONS})
.test.each(
[Noop, ViewNativeComponent, View],
Component =>
`render ${NUMBER_OF_VIEWS} views (${Component.displayName ?? Component.name ?? 'ViewNativeComponent'})`,
() => {
return {
overriddenDuration: measureRenderTime(root, testViews),
};
},
Component => ({
beforeAll: () => {
let views: React.Node = null;
for (let i = 0; i < NUMBER_OF_VIEWS; i++) {
views = (
<Component style={{width: i + 1, height: i + 1}}>{views}</Component>
);
}
// $FlowExpectedError[incompatible-type]
testViews = views;
},
beforeEach: () => {
root = Fantom.createRoot();
},
afterEach: () => {
root.destroy();
},
}),
);
@@ -0,0 +1,46 @@
/**
* 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.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import * as Fantom from '@react-native/fantom';
import nullthrows from 'nullthrows';
const now = Fantom.unstable_benchmark.now;
export default function measureRenderTime(
root: Fantom.Root,
elements: React.MixedElement,
): number {
let startTime: ?number;
let endTime: ?number;
function RecordStart() {
startTime ??= now();
return null;
}
function RecordEnd() {
endTime = now();
return null;
}
Fantom.runTask(() => {
// React renders nodes using a depth-first algorithm.
root.render(
<>
<RecordStart />
{elements}
<RecordEnd />
</>,
);
});
return nullthrows(endTime) - nullthrows(startTime);
}