diff --git a/packages/react-native/Libraries/Components/View/__tests__/View-benchmark-itest.js b/packages/react-native/Libraries/Components/View/__tests__/View-benchmark-itest.js
index 5d19b6c52a1e..10930caf2f98 100644
--- a/packages/react-native/Libraries/Components/View/__tests__/View-benchmark-itest.js
+++ b/packages/react-native/Libraries/Components/View/__tests__/View-benchmark-itest.js
@@ -17,6 +17,31 @@ import {View} from 'react-native';
let root;
let testViews: React.MixedElement;
+function createDeepViewHierarchy(depth: number, breadth: number): React.Node {
+ if (depth === 0) {
+ return (
+
+ );
+ }
+ const children = [];
+ for (let i = 0; i < breadth; i++) {
+ children.push(
+
+ {createDeepViewHierarchy(depth - 1, breadth)}
+ ,
+ );
+ }
+ return {children};
+}
+
function createViewsWithLargeAmountOfPropsAndStyles(count: number): React.Node {
let views: React.Node = null;
for (let i = 0; i < count; i++) {
@@ -124,4 +149,28 @@ Fantom.unstable_benchmark
root.destroy();
},
}),
+ )
+ .test.each(
+ [
+ [5, 4],
+ [7, 3],
+ [10, 2],
+ ],
+ ([depth, breadth]) =>
+ `render deep view hierarchy (depth=${depth.toString()}, breadth=${breadth.toString()})`,
+ () => {
+ Fantom.runTask(() => root.render(testViews));
+ },
+ ([depth, breadth]) => ({
+ beforeAll: () => {
+ // $FlowExpectedError[incompatible-type]
+ testViews = createDeepViewHierarchy(depth, breadth);
+ },
+ beforeEach: () => {
+ root = Fantom.createRoot();
+ },
+ afterEach: () => {
+ root.destroy();
+ },
+ }),
);
diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt
index 3e0e99e6af11..a2295ba531ec 100644
--- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt
+++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
- * @generated SignedSource<<477777b9a795b57f3bb3eaeb030738a9>>
+ * @generated SignedSource<<156d4f5f35037184b6fc61ff1d856028>>
*/
/**
@@ -516,6 +516,12 @@ public object ReactNativeFeatureFlags {
@JvmStatic
public fun useTurboModules(): Boolean = accessor.useTurboModules()
+ /**
+ * Use std::unordered_map instead of TinyMap in the Differentiator for improved lookup performance.
+ */
+ @JvmStatic
+ public fun useUnorderedMapInDifferentiator(): Boolean = accessor.useUnorderedMapInDifferentiator()
+
/**
* Outset the culling context frame with the provided ratio. The culling context frame size will be outset by width * ratio on the left and right, and height * ratio on the top and bottom.
*/
diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt
index bbd27b4d6433..df54de170654 100644
--- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt
+++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
- * @generated SignedSource<>
+ * @generated SignedSource<<0875d5e54d884a26d37bb4eb2acc57d5>>
*/
/**
@@ -101,6 +101,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
private var useTraitHiddenOnAndroidCache: Boolean? = null
private var useTurboModuleInteropCache: Boolean? = null
private var useTurboModulesCache: Boolean? = null
+ private var useUnorderedMapInDifferentiatorCache: Boolean? = null
private var viewCullingOutsetRatioCache: Double? = null
private var viewTransitionEnabledCache: Boolean? = null
private var virtualViewPrerenderRatioCache: Double? = null
@@ -834,6 +835,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
return cached
}
+ override fun useUnorderedMapInDifferentiator(): Boolean {
+ var cached = useUnorderedMapInDifferentiatorCache
+ if (cached == null) {
+ cached = ReactNativeFeatureFlagsCxxInterop.useUnorderedMapInDifferentiator()
+ useUnorderedMapInDifferentiatorCache = cached
+ }
+ return cached
+ }
+
override fun viewCullingOutsetRatio(): Double {
var cached = viewCullingOutsetRatioCache
if (cached == null) {
diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt
index 5aa0113e15e4..a9e7550867b7 100644
--- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt
+++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
- * @generated SignedSource<>
+ * @generated SignedSource<<948a9beebe2ff00791a03455eb774eee>>
*/
/**
@@ -190,6 +190,8 @@ public object ReactNativeFeatureFlagsCxxInterop {
@DoNotStrip @JvmStatic public external fun useTurboModules(): Boolean
+ @DoNotStrip @JvmStatic public external fun useUnorderedMapInDifferentiator(): Boolean
+
@DoNotStrip @JvmStatic public external fun viewCullingOutsetRatio(): Double
@DoNotStrip @JvmStatic public external fun viewTransitionEnabled(): Boolean
diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt
index 0950fb163232..b7e15d740909 100644
--- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt
+++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
- * @generated SignedSource<<523e3c35d4bd1fc85f2a3bb26b8aad3f>>
+ * @generated SignedSource<<89c61520177334f93c65ff92c2fc74a6>>
*/
/**
@@ -185,6 +185,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
override fun useTurboModules(): Boolean = false
+ override fun useUnorderedMapInDifferentiator(): Boolean = false
+
override fun viewCullingOutsetRatio(): Double = 0.0
override fun viewTransitionEnabled(): Boolean = false
diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt
index 656dbba20689..48cb60dc5894 100644
--- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt
+++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
- * @generated SignedSource<>
+ * @generated SignedSource<<669708c311abe9ffc8f7783219e2baad>>
*/
/**
@@ -105,6 +105,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
private var useTraitHiddenOnAndroidCache: Boolean? = null
private var useTurboModuleInteropCache: Boolean? = null
private var useTurboModulesCache: Boolean? = null
+ private var useUnorderedMapInDifferentiatorCache: Boolean? = null
private var viewCullingOutsetRatioCache: Double? = null
private var viewTransitionEnabledCache: Boolean? = null
private var virtualViewPrerenderRatioCache: Double? = null
@@ -919,6 +920,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
return cached
}
+ override fun useUnorderedMapInDifferentiator(): Boolean {
+ var cached = useUnorderedMapInDifferentiatorCache
+ if (cached == null) {
+ cached = currentProvider.useUnorderedMapInDifferentiator()
+ accessedFeatureFlags.add("useUnorderedMapInDifferentiator")
+ useUnorderedMapInDifferentiatorCache = cached
+ }
+ return cached
+ }
+
override fun viewCullingOutsetRatio(): Double {
var cached = viewCullingOutsetRatioCache
if (cached == null) {
diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt
index bb2f3ac62adf..e0b05b9dde3c 100644
--- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt
+++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
- * @generated SignedSource<>
+ * @generated SignedSource<>
*/
/**
@@ -185,6 +185,8 @@ public interface ReactNativeFeatureFlagsProvider {
@DoNotStrip public fun useTurboModules(): Boolean
+ @DoNotStrip public fun useUnorderedMapInDifferentiator(): Boolean
+
@DoNotStrip public fun viewCullingOutsetRatio(): Double
@DoNotStrip public fun viewTransitionEnabled(): Boolean
diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp
index 15c606bb8072..7a60a11c7279 100644
--- a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp
+++ b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
- * @generated SignedSource<<45063df01d7ce8726b4a7d901f1b3341>>
+ * @generated SignedSource<<0b95d68522d63d51d3e524aeecff246a>>
*/
/**
@@ -525,6 +525,12 @@ class ReactNativeFeatureFlagsJavaProvider
return method(javaProvider_);
}
+ bool useUnorderedMapInDifferentiator() override {
+ static const auto method =
+ getReactNativeFeatureFlagsProviderJavaClass()->getMethod("useUnorderedMapInDifferentiator");
+ return method(javaProvider_);
+ }
+
double viewCullingOutsetRatio() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod("viewCullingOutsetRatio");
@@ -952,6 +958,11 @@ bool JReactNativeFeatureFlagsCxxInterop::useTurboModules(
return ReactNativeFeatureFlags::useTurboModules();
}
+bool JReactNativeFeatureFlagsCxxInterop::useUnorderedMapInDifferentiator(
+ facebook::jni::alias_ref /*unused*/) {
+ return ReactNativeFeatureFlags::useUnorderedMapInDifferentiator();
+}
+
double JReactNativeFeatureFlagsCxxInterop::viewCullingOutsetRatio(
facebook::jni::alias_ref /*unused*/) {
return ReactNativeFeatureFlags::viewCullingOutsetRatio();
@@ -1241,6 +1252,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() {
makeNativeMethod(
"useTurboModules",
JReactNativeFeatureFlagsCxxInterop::useTurboModules),
+ makeNativeMethod(
+ "useUnorderedMapInDifferentiator",
+ JReactNativeFeatureFlagsCxxInterop::useUnorderedMapInDifferentiator),
makeNativeMethod(
"viewCullingOutsetRatio",
JReactNativeFeatureFlagsCxxInterop::viewCullingOutsetRatio),
diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h
index f5cd383bdeeb..67889af8a636 100644
--- a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h
+++ b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
- * @generated SignedSource<<5ac93ed057017f8d1a388b8029614f18>>
+ * @generated SignedSource<<6b7e2af51ba9d64ae4e474dfa104a7c3>>
*/
/**
@@ -273,6 +273,9 @@ class JReactNativeFeatureFlagsCxxInterop
static bool useTurboModules(
facebook::jni::alias_ref);
+ static bool useUnorderedMapInDifferentiator(
+ facebook::jni::alias_ref);
+
static double viewCullingOutsetRatio(
facebook::jni::alias_ref);
diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp
index 37f971aad1bb..0c045bcf92d2 100644
--- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp
+++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
- * @generated SignedSource<<9d81f74c5926706ee353813e594575e8>>
+ * @generated SignedSource<<08a361f2ffac6a0496adac1d4c3e4726>>
*/
/**
@@ -350,6 +350,10 @@ bool ReactNativeFeatureFlags::useTurboModules() {
return getAccessor().useTurboModules();
}
+bool ReactNativeFeatureFlags::useUnorderedMapInDifferentiator() {
+ return getAccessor().useUnorderedMapInDifferentiator();
+}
+
double ReactNativeFeatureFlags::viewCullingOutsetRatio() {
return getAccessor().viewCullingOutsetRatio();
}
diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h
index 8a1ca2766b22..af8e15b1e6ed 100644
--- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h
+++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
- * @generated SignedSource<<84e2800073ffab2313a4e27897c0c246>>
+ * @generated SignedSource<<0bbe4d41581432dfad7adbc2db133d00>>
*/
/**
@@ -444,6 +444,11 @@ class ReactNativeFeatureFlags {
*/
RN_EXPORT static bool useTurboModules();
+ /**
+ * Use std::unordered_map instead of TinyMap in the Differentiator for improved lookup performance.
+ */
+ RN_EXPORT static bool useUnorderedMapInDifferentiator();
+
/**
* Outset the culling context frame with the provided ratio. The culling context frame size will be outset by width * ratio on the left and right, and height * ratio on the top and bottom.
*/
diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp
index aafd52dd5889..de6c64c0be12 100644
--- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp
+++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
- * @generated SignedSource<<8f9f3ced66040f8275073e5084765356>>
+ * @generated SignedSource<<3dd9492ca660ad6350ce6ee4a9b5e310>>
*/
/**
@@ -1487,6 +1487,24 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModules() {
return flagValue.value();
}
+bool ReactNativeFeatureFlagsAccessor::useUnorderedMapInDifferentiator() {
+ auto flagValue = useUnorderedMapInDifferentiator_.load();
+
+ if (!flagValue.has_value()) {
+ // This block is not exclusive but it is not necessary.
+ // If multiple threads try to initialize the feature flag, we would only
+ // be accessing the provider multiple times but the end state of this
+ // instance and the returned flag value would be the same.
+
+ markFlagAsAccessed(81, "useUnorderedMapInDifferentiator");
+
+ flagValue = currentProvider_->useUnorderedMapInDifferentiator();
+ useUnorderedMapInDifferentiator_ = flagValue;
+ }
+
+ return flagValue.value();
+}
+
double ReactNativeFeatureFlagsAccessor::viewCullingOutsetRatio() {
auto flagValue = viewCullingOutsetRatio_.load();
@@ -1496,7 +1514,7 @@ double ReactNativeFeatureFlagsAccessor::viewCullingOutsetRatio() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
- markFlagAsAccessed(81, "viewCullingOutsetRatio");
+ markFlagAsAccessed(82, "viewCullingOutsetRatio");
flagValue = currentProvider_->viewCullingOutsetRatio();
viewCullingOutsetRatio_ = flagValue;
@@ -1514,7 +1532,7 @@ bool ReactNativeFeatureFlagsAccessor::viewTransitionEnabled() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
- markFlagAsAccessed(82, "viewTransitionEnabled");
+ markFlagAsAccessed(83, "viewTransitionEnabled");
flagValue = currentProvider_->viewTransitionEnabled();
viewTransitionEnabled_ = flagValue;
@@ -1532,7 +1550,7 @@ double ReactNativeFeatureFlagsAccessor::virtualViewPrerenderRatio() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
- markFlagAsAccessed(83, "virtualViewPrerenderRatio");
+ markFlagAsAccessed(84, "virtualViewPrerenderRatio");
flagValue = currentProvider_->virtualViewPrerenderRatio();
virtualViewPrerenderRatio_ = flagValue;
diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h
index 28f655e04c0e..b3784c81fe63 100644
--- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h
+++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
- * @generated SignedSource<<6ba661fd9ce6aeff6cc9269848230618>>
+ * @generated SignedSource<<2cb2c124f044468f96262086bfac5aad>>
*/
/**
@@ -113,6 +113,7 @@ class ReactNativeFeatureFlagsAccessor {
bool useTraitHiddenOnAndroid();
bool useTurboModuleInterop();
bool useTurboModules();
+ bool useUnorderedMapInDifferentiator();
double viewCullingOutsetRatio();
bool viewTransitionEnabled();
double virtualViewPrerenderRatio();
@@ -127,7 +128,7 @@ class ReactNativeFeatureFlagsAccessor {
std::unique_ptr currentProvider_;
bool wasOverridden_;
- std::array, 84> accessedFeatureFlags_;
+ std::array, 85> accessedFeatureFlags_;
std::atomic> commonTestFlag_;
std::atomic> cdpInteractionMetricsEnabled_;
@@ -210,6 +211,7 @@ class ReactNativeFeatureFlagsAccessor {
std::atomic> useTraitHiddenOnAndroid_;
std::atomic> useTurboModuleInterop_;
std::atomic> useTurboModules_;
+ std::atomic> useUnorderedMapInDifferentiator_;
std::atomic> viewCullingOutsetRatio_;
std::atomic> viewTransitionEnabled_;
std::atomic> virtualViewPrerenderRatio_;
diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h
index e17c94e91f53..75ba8e143e40 100644
--- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h
+++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
- * @generated SignedSource<<861e4a47ad9b2aa4ac054059082724a0>>
+ * @generated SignedSource<<7f1c4037925fc37dcdcba51df968d503>>
*/
/**
@@ -351,6 +351,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider {
return false;
}
+ bool useUnorderedMapInDifferentiator() override {
+ return false;
+ }
+
double viewCullingOutsetRatio() override {
return 0.0;
}
diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h
index 56c0e084c0f9..62a9247ac77d 100644
--- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h
+++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
- * @generated SignedSource<<774ffd15e1fd9a79bb7b3f6c4719ba23>>
+ * @generated SignedSource<>
*/
/**
@@ -774,6 +774,15 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef
return ReactNativeFeatureFlagsDefaults::useTurboModules();
}
+ bool useUnorderedMapInDifferentiator() override {
+ auto value = values_["useUnorderedMapInDifferentiator"];
+ if (!value.isNull()) {
+ return value.getBool();
+ }
+
+ return ReactNativeFeatureFlagsDefaults::useUnorderedMapInDifferentiator();
+ }
+
double viewCullingOutsetRatio() override {
auto value = values_["viewCullingOutsetRatio"];
if (!value.isNull()) {
diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h
index cadf99532f4c..f1cff605711a 100644
--- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h
+++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
- * @generated SignedSource<>
+ * @generated SignedSource<<43e301c5028a2ed195e2a3d0a4cd0ab3>>
*/
/**
@@ -106,6 +106,7 @@ class ReactNativeFeatureFlagsProvider {
virtual bool useTraitHiddenOnAndroid() = 0;
virtual bool useTurboModuleInterop() = 0;
virtual bool useTurboModules() = 0;
+ virtual bool useUnorderedMapInDifferentiator() = 0;
virtual double viewCullingOutsetRatio() = 0;
virtual bool viewTransitionEnabled() = 0;
virtual double virtualViewPrerenderRatio() = 0;
diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp
index 2d845642de15..34101508393f 100644
--- a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp
+++ b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
- * @generated SignedSource<<0356babbe4e65071e6cc56a06e68b944>>
+ * @generated SignedSource<<006c43555032f785207c6c013f1974f5>>
*/
/**
@@ -449,6 +449,11 @@ bool NativeReactNativeFeatureFlags::useTurboModules(
return ReactNativeFeatureFlags::useTurboModules();
}
+bool NativeReactNativeFeatureFlags::useUnorderedMapInDifferentiator(
+ jsi::Runtime& /*runtime*/) {
+ return ReactNativeFeatureFlags::useUnorderedMapInDifferentiator();
+}
+
double NativeReactNativeFeatureFlags::viewCullingOutsetRatio(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::viewCullingOutsetRatio();
diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h
index bfb867589ab0..6675417c0e51 100644
--- a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h
+++ b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
- * @generated SignedSource<<2b82eb6d91d0b4437aa3b25f0488fe3e>>
+ * @generated SignedSource<<525d64b15e1b72440743363280116c6a>>
*/
/**
@@ -198,6 +198,8 @@ class NativeReactNativeFeatureFlags
bool useTurboModules(jsi::Runtime& runtime);
+ bool useUnorderedMapInDifferentiator(jsi::Runtime& runtime);
+
double viewCullingOutsetRatio(jsi::Runtime& runtime);
bool viewTransitionEnabled(jsi::Runtime& runtime);
diff --git a/packages/react-native/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.cpp b/packages/react-native/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.cpp
index 8abb96cfcb97..320bbfe0bcfc 100644
--- a/packages/react-native/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.cpp
+++ b/packages/react-native/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.cpp
@@ -256,8 +256,7 @@ LayoutAnimationKeyFrameManager::pullTransaction(
// Catch delete+create (reparenting) (this should be optimized away at
// the diffing level eventually?)
// TODO: to prevent this step we could tag Remove/Insert mutations as
- // being moves on the Differ level, since we know that there? We could use
- // TinyMap here, but it's not exposed by Differentiator (yet).
+ // being moves on the Differ level, since we know that there?
std::unordered_set insertedTags;
std::unordered_set deletedTags;
std::unordered_set
diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/Differentiator.cpp b/packages/react-native/ReactCommon/react/renderer/mounting/Differentiator.cpp
index dff263a34981..d3c7c92376fd 100644
--- a/packages/react-native/ReactCommon/react/renderer/mounting/Differentiator.cpp
+++ b/packages/react-native/ReactCommon/react/renderer/mounting/Differentiator.cpp
@@ -9,11 +9,10 @@
#include
#include
-#include
#include
#include "internal/CullingContext.h"
+#include "internal/DiffMap.h"
#include "internal/ShadowViewNodePair.h"
-#include "internal/TinyMap.h"
#include "internal/sliceChildShadowNodeViewPairs.h"
#include "ShadowView.h"
@@ -58,7 +57,7 @@ static std::ostream& operator<<(
#ifdef DEBUG_LOGS_DIFFER
template
-static std::ostream& operator<<(std::ostream& out, TinyMap& map) {
+static std::ostream& operator<<(std::ostream& out, DiffMap& map) {
auto it = map.begin();
if (it != map.end()) {
out << *it->second;
@@ -143,7 +142,7 @@ struct OrderedMutationInstructionContainer {
static void updateMatchedPairSubtrees(
ViewNodePairScope& scope,
OrderedMutationInstructionContainer& mutationContainer,
- TinyMap& newRemainingPairs,
+ DiffMap& newRemainingPairs,
std::vector& oldChildPairs,
Tag parentTag,
const ShadowViewNodePair& oldPair,
@@ -164,11 +163,11 @@ static void calculateShadowViewMutationsFlattener(
ReparentMode reparentMode,
OrderedMutationInstructionContainer& mutationContainer,
Tag parentTag,
- TinyMap& unvisitedOtherNodes,
+ DiffMap& unvisitedOtherNodes,
const ShadowViewNodePair& node,
Tag parentTagForUpdate,
- TinyMap* parentSubVisitedOtherNewNodes,
- TinyMap* parentSubVisitedOtherOldNodes,
+ DiffMap* parentSubVisitedOtherNewNodes,
+ DiffMap* parentSubVisitedOtherOldNodes,
const CullingContext& cullingContextForUnvisitedOtherNodes,
const CullingContext& cullingContext);
@@ -183,7 +182,7 @@ static void calculateShadowViewMutationsFlattener(
static void updateMatchedPairSubtrees(
ViewNodePairScope& scope,
OrderedMutationInstructionContainer& mutationContainer,
- TinyMap& newRemainingPairs,
+ DiffMap& newRemainingPairs,
std::vector& oldChildPairs,
Tag parentTag,
const ShadowViewNodePair& oldPair,
@@ -232,7 +231,6 @@ static void updateMatchedPairSubtrees(
// Unflattening
else {
// Construct unvisited nodes map
- auto unvisitedOldChildPairs = TinyMap{};
// We don't know where all the children of oldChildPair are
// within oldChildPairs, but we know that they're in the same
// relative order. The reason for this is because of flattening
@@ -240,6 +238,8 @@ static void updateMatchedPairSubtrees(
// interwoven with children from other nodes, etc.
auto oldFlattenedNodes = sliceChildShadowNodeViewPairsFromViewNodePair(
oldPair, scope, true, oldCullingContextCopy);
+ auto unvisitedOldChildPairs =
+ DiffMap(oldFlattenedNodes.size());
for (size_t i = 0, j = 0;
i < oldChildPairs.size() && j < oldFlattenedNodes.size();
i++) {
@@ -422,11 +422,11 @@ static void calculateShadowViewMutationsFlattener(
ReparentMode reparentMode,
OrderedMutationInstructionContainer& mutationContainer,
Tag parentTag,
- TinyMap& unvisitedOtherNodes,
+ DiffMap& unvisitedOtherNodes,
const ShadowViewNodePair& node,
Tag parentTagForUpdate,
- TinyMap* parentSubVisitedOtherNewNodes,
- TinyMap* parentSubVisitedOtherOldNodes,
+ DiffMap* parentSubVisitedOtherNewNodes,
+ DiffMap* parentSubVisitedOtherOldNodes,
const CullingContext& cullingContextForUnvisitedOtherNodes,
const CullingContext& cullingContext) {
// Step 1: iterate through entire tree
@@ -445,8 +445,8 @@ static void calculateShadowViewMutationsFlattener(
// Views in other tree that are visited by sub-flattening or
// sub-unflattening
- TinyMap subVisitedOtherNewNodes{};
- TinyMap subVisitedOtherOldNodes{};
+ DiffMap subVisitedOtherNewNodes{};
+ DiffMap subVisitedOtherOldNodes{};
auto subVisitedNewMap =
(parentSubVisitedOtherNewNodes != nullptr ? parentSubVisitedOtherNewNodes
: &subVisitedOtherNewNodes);
@@ -456,7 +456,7 @@ static void calculateShadowViewMutationsFlattener(
// Candidates for full tree creation or deletion at the end of this function
auto deletionCreationCandidatePairs =
- TinyMap{};
+ DiffMap(treeChildren.size());
for (size_t index = 0;
index < treeChildren.size() && index < treeChildren.size();
@@ -471,7 +471,7 @@ static void calculateShadowViewMutationsFlattener(
: subVisitedNewMap->end());
auto subVisitedOtherOldIt =
(unvisitedIt == unvisitedOtherNodes.end() &&
- (subVisitedNewMap->end() != nullptr)
+ subVisitedOtherNewIt == subVisitedNewMap->end()
? subVisitedOldMap->find(treeChildPair.shadowView.tag)
: subVisitedOldMap->end());
@@ -697,7 +697,7 @@ static void calculateShadowViewMutationsFlattener(
: adjustedOldCullingContext);
// Construct unvisited nodes map
auto unvisitedRecursiveChildPairs =
- TinyMap{};
+ DiffMap(flattenedNodes.size());
for (auto& flattenedNode : flattenedNodes) {
auto& newChild = *flattenedNode;
@@ -756,9 +756,6 @@ static void calculateShadowViewMutationsFlattener(
// loop of this function.
for (auto& unvisitedRecursiveChildPair :
unvisitedRecursiveChildPairs) {
- if (unvisitedRecursiveChildPair.first == 0) {
- continue;
- }
auto& oldFlattenedNode = *unvisitedRecursiveChildPair.second;
// Node unvisited - mark the entire subtree for deletion
@@ -820,9 +817,6 @@ static void calculateShadowViewMutationsFlattener(
// subtrees if they were never visited during the execution of the above
// loop and recursions.
for (auto& deletionCreationCandidatePair : deletionCreationCandidatePairs) {
- if (deletionCreationCandidatePair.first == 0) {
- continue;
- }
auto& treeChildPair = *deletionCreationCandidatePair.second;
// If node was visited during a flattening/unflattening recursion,
@@ -1042,9 +1036,10 @@ static void calculateShadowViewMutations(
}
} else {
// Collect map of tags in the new list
- auto newRemainingPairs = TinyMap{};
- auto newInsertedPairs = TinyMap{};
- auto deletionCandidatePairs = TinyMap{};
+ auto remainingCount = newChildPairs.size() - index;
+ auto newRemainingPairs = DiffMap(remainingCount);
+ auto newInsertedPairs = DiffMap(remainingCount);
+ auto deletionCandidatePairs = DiffMap{};
for (; index < newChildPairs.size(); index++) {
auto& newChildPair = *newChildPairs[index];
newRemainingPairs.insert({newChildPair.shadowView.tag, &newChildPair});
@@ -1243,10 +1238,6 @@ static void calculateShadowViewMutations(
// list to make sure that a node was not reparented into an unflattened
// node that occurs *after* it in the hierarchy, due to zIndex ordering.
for (auto& deletionCandidatePair : deletionCandidatePairs) {
- if (deletionCandidatePair.first == 0) {
- continue;
- }
-
const auto& oldChildPair = *deletionCandidatePair.second;
DEBUG_LOGS({
@@ -1285,14 +1276,6 @@ static void calculateShadowViewMutations(
// Final step: generate Create instructions for entirely new
// subtrees/nodes that are not the result of flattening or unflattening.
for (auto& newInsertedPair : newInsertedPairs) {
- // Erased elements of a TinyMap will have a Tag/key of 0 - skip those
- // These *should* be removed by the map; there are currently no KNOWN
- // cases where TinyMap will do the wrong thing, but there are not yet
- // any unit tests explicitly for TinyMap, so this is safer for now.
- if (newInsertedPair.first == 0) {
- continue;
- }
-
const auto& newChildPair = *newInsertedPair.second;
DEBUG_LOGS({
diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/internal/DiffMap.h b/packages/react-native/ReactCommon/react/renderer/mounting/internal/DiffMap.h
new file mode 100644
index 000000000000..14e05f685f9e
--- /dev/null
+++ b/packages/react-native/ReactCommon/react/renderer/mounting/internal/DiffMap.h
@@ -0,0 +1,170 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include
+#include
+#include
+#include "TinyMap.h"
+
+namespace facebook::react {
+
+/**
+ * A facade that delegates to either TinyMap or std::unordered_map based on
+ * the useUnorderedMapInDifferentiator feature flag. This allows safe rollout
+ * of the unordered_map change in the Differentiator.
+ *
+ * Exposes a subset of the std::unordered_map interface used by the
+ * Differentiator: find, insert, erase, begin, end, and range-for.
+ *
+ * The Iterator returns const references because it uses a cached copy
+ * internally (the two backends have different pair types). The Differentiator
+ * only reads through iterators, never writes.
+ */
+template
+class DiffMap final {
+ public:
+ using Pair = std::pair;
+
+ class Iterator {
+ public:
+ const Pair &operator*() const
+ {
+ syncCached();
+ return cached_;
+ }
+
+ const Pair *operator->() const
+ {
+ syncCached();
+ return &cached_;
+ }
+
+ Iterator &operator++()
+ {
+ if (useUnorderedMap_) {
+ ++umIt_;
+ } else {
+ ++tinyIt_;
+ }
+ dirty_ = true;
+ return *this;
+ }
+
+ bool operator==(const Iterator &other) const
+ {
+ if (useUnorderedMap_) {
+ return umIt_ == other.umIt_;
+ }
+ return tinyIt_ == other.tinyIt_;
+ }
+
+ bool operator!=(const Iterator &other) const
+ {
+ return !(*this == other);
+ }
+
+ private:
+ friend class DiffMap;
+
+ void syncCached() const
+ {
+ if (!dirty_) {
+ return;
+ }
+ if (useUnorderedMap_) {
+ cached_ = {umIt_->first, umIt_->second};
+ } else {
+ cached_ = {tinyIt_->first, tinyIt_->second};
+ }
+ dirty_ = false;
+ }
+
+ bool useUnorderedMap_{false};
+ typename TinyMap::Iterator tinyIt_{nullptr};
+ typename std::unordered_map::iterator umIt_{};
+ mutable Pair cached_{};
+ mutable bool dirty_{true};
+ };
+
+ DiffMap() : useUnorderedMap_(ReactNativeFeatureFlags::useUnorderedMapInDifferentiator())
+ {
+ if (useUnorderedMap_) {
+ unorderedMap_.emplace();
+ }
+ }
+
+ explicit DiffMap(size_t sizeHint) : useUnorderedMap_(ReactNativeFeatureFlags::useUnorderedMapInDifferentiator())
+ {
+ if (useUnorderedMap_) {
+ unorderedMap_.emplace();
+ unorderedMap_->reserve(sizeHint);
+ }
+ }
+
+ Iterator begin()
+ {
+ Iterator it;
+ it.useUnorderedMap_ = useUnorderedMap_;
+ if (useUnorderedMap_) {
+ it.umIt_ = unorderedMap_->begin();
+ } else {
+ it.tinyIt_ = tinyMap_.begin();
+ }
+ return it;
+ }
+
+ Iterator end()
+ {
+ Iterator it;
+ it.useUnorderedMap_ = useUnorderedMap_;
+ if (useUnorderedMap_) {
+ it.umIt_ = unorderedMap_->end();
+ } else {
+ it.tinyIt_ = tinyMap_.end();
+ }
+ return it;
+ }
+
+ Iterator find(KeyT key)
+ {
+ Iterator it;
+ it.useUnorderedMap_ = useUnorderedMap_;
+ if (useUnorderedMap_) {
+ it.umIt_ = unorderedMap_->find(key);
+ } else {
+ it.tinyIt_ = tinyMap_.find(key);
+ }
+ return it;
+ }
+
+ void insert(Pair pair)
+ {
+ if (useUnorderedMap_) {
+ unorderedMap_->insert(std::move(pair));
+ } else {
+ tinyMap_.insert(std::move(pair));
+ }
+ }
+
+ void erase(Iterator it)
+ {
+ if (useUnorderedMap_) {
+ unorderedMap_->erase(it.umIt_);
+ } else {
+ tinyMap_.erase(it.tinyIt_);
+ }
+ }
+
+ private:
+ bool useUnorderedMap_;
+ TinyMap tinyMap_;
+ std::optional> unorderedMap_;
+};
+
+} // namespace facebook::react
diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/internal/sliceChildShadowNodeViewPairs.h b/packages/react-native/ReactCommon/react/renderer/mounting/internal/sliceChildShadowNodeViewPairs.h
index 869da1d5de15..b85b1e13be46 100644
--- a/packages/react-native/ReactCommon/react/renderer/mounting/internal/sliceChildShadowNodeViewPairs.h
+++ b/packages/react-native/ReactCommon/react/renderer/mounting/internal/sliceChildShadowNodeViewPairs.h
@@ -18,10 +18,10 @@ struct ShadowViewNodePair;
/**
* During differ, we need to keep some `ShadowViewNodePair`s in memory.
* Some `ShadowViewNodePair`s are referenced from std::vectors returned
- * by `sliceChildShadowNodeViewPairs`; some are referenced in TinyMaps
+ * by `sliceChildShadowNodeViewPairs`; some are referenced in maps
* for view (un)flattening especially; and it is not always clear which
- * std::vectors will outlive which TinyMaps, and vice-versa, so it doesn't
- * make sense for the std::vector or TinyMap to own any `ShadowViewNodePair`s.
+ * std::vectors will outlive which maps, and vice-versa, so it doesn't
+ * make sense for the std::vector or map to own any `ShadowViewNodePair`s.
*
* Thus, we introduce the concept of a scope.
*
diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js
index e8e429ba609c..04154d728767 100644
--- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js
+++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js
@@ -911,6 +911,17 @@ const definitions: FeatureFlagDefinitions = {
},
ossReleaseStage: 'canary',
},
+ useUnorderedMapInDifferentiator: {
+ defaultValue: false,
+ metadata: {
+ dateAdded: '2026-02-26',
+ description:
+ 'Use std::unordered_map instead of TinyMap in the Differentiator for improved lookup performance.',
+ expectedReleaseValue: true,
+ purpose: 'experimentation',
+ },
+ ossReleaseStage: 'none',
+ },
viewCullingOutsetRatio: {
defaultValue: 0,
metadata: {
diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js
index 7939dec2f939..e2e66482fde4 100644
--- a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js
+++ b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
- * @generated SignedSource<>
+ * @generated SignedSource<<8cd3151384989b1a4e8ec204943e2435>>
* @flow strict
* @noformat
*/
@@ -128,6 +128,7 @@ export type ReactNativeFeatureFlags = $ReadOnly<{
useTraitHiddenOnAndroid: Getter,
useTurboModuleInterop: Getter,
useTurboModules: Getter,
+ useUnorderedMapInDifferentiator: Getter,
viewCullingOutsetRatio: Getter,
viewTransitionEnabled: Getter,
virtualViewPrerenderRatio: Getter,
@@ -521,6 +522,10 @@ export const useTurboModuleInterop: Getter = createNativeFlagGetter('us
* When enabled, NativeModules will be executed by using the TurboModule system
*/
export const useTurboModules: Getter = createNativeFlagGetter('useTurboModules', false);
+/**
+ * Use std::unordered_map instead of TinyMap in the Differentiator for improved lookup performance.
+ */
+export const useUnorderedMapInDifferentiator: Getter = createNativeFlagGetter('useUnorderedMapInDifferentiator', false);
/**
* Outset the culling context frame with the provided ratio. The culling context frame size will be outset by width * ratio on the left and right, and height * ratio on the top and bottom.
*/
diff --git a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js
index 128f27b7f32e..4b47cc872f5c 100644
--- a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js
+++ b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
- * @generated SignedSource<<3495a8cf9d3d820e37d082b3d62e2b91>>
+ * @generated SignedSource<<2955ab3f744af8b5cdf587312ba423d7>>
* @flow strict
* @noformat
*/
@@ -106,6 +106,7 @@ export interface Spec extends TurboModule {
+useTraitHiddenOnAndroid?: () => boolean;
+useTurboModuleInterop?: () => boolean;
+useTurboModules?: () => boolean;
+ +useUnorderedMapInDifferentiator?: () => boolean;
+viewCullingOutsetRatio?: () => number;
+viewTransitionEnabled?: () => boolean;
+virtualViewPrerenderRatio?: () => number;