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;