Skip to content

Commit 8c01b56

Browse files
mdvaccafacebook-github-bot
authored andcommitted
Improve caching of Text per shadow Node
Summary: This diff optimizes text measurement by adding a layer of caching per shadow node. When ReactFeatureFlags.enableTextMeasureCachePerShadowNode is enabled, we will cache and reused measurements per shadow node. This optimization showed a 2x improvement on text rendearing when a screen contains a big amount of text components. Changelog: [Internal] Internal Reviewed By: sammy-SC Differential Revision: D44221170 fbshipit-source-id: c3e7ba1ad216929826a99585874981717c90e13b
1 parent a999f0d commit 8c01b56

File tree

7 files changed

+59
-6
lines changed

7 files changed

+59
-6
lines changed

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java

+3
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ public class ReactFeatureFlags {
7575
/** Feature Flag to enable the pending event queue in fabric before mounting views */
7676
public static boolean enableFabricPendingEventQueue = false;
7777

78+
/** Feature Flag to enable caching mechanism of text measurement at shadow node level */
79+
public static boolean enableTextMeasureCachePerShadowNode = false;
80+
7881
/**
7982
* Feature flag that controls how turbo modules are exposed to JS
8083
*

packages/react-native/ReactAndroid/src/main/jni/react/fabric/Binding.cpp

+3
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,9 @@ void Binding::installFabricUIManager(
429429
"CalculateTransformedFramesEnabled",
430430
getFeatureFlagValue("calculateTransformedFramesEnabled"));
431431

432+
CoreFeatures::cacheLastTextMeasurement =
433+
getFeatureFlagValue("enableTextMeasureCachePerShadowNode");
434+
432435
// Props setter pattern feature
433436
CoreFeatures::enablePropIteratorSetter =
434437
getFeatureFlagValue("enableCppPropsIteratorSetter");

packages/react-native/ReactCommon/react/renderer/components/text/ParagraphLayoutManager.cpp

+30-5
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,19 @@ TextMeasurement ParagraphLayoutManager::measure(
1515
AttributedString const &attributedString,
1616
ParagraphAttributes const &paragraphAttributes,
1717
LayoutConstraints layoutConstraints) const {
18+
bool cacheLastTextMeasurement = CoreFeatures::cacheLastTextMeasurement;
19+
if (cacheLastTextMeasurement &&
20+
(layoutConstraints.maximumSize.width == availableWidth_ ||
21+
layoutConstraints.maximumSize.width ==
22+
cachedTextMeasurement_.size.width)) {
23+
/* Yoga has requested measurement for this size before. Let's use cached
24+
* value. `TextLayoutManager` might not have cached this because it could be
25+
* using different width to generate cache key. This happens because Yoga
26+
* switches between available width and exact width but since we already
27+
* know exact width, it is wasteful to calculate it again.
28+
*/
29+
return cachedTextMeasurement_;
30+
}
1831
if (CoreFeatures::cacheNSTextStorage) {
1932
size_t newHash = folly::hash::hash_combine(
2033
0,
@@ -28,11 +41,23 @@ TextMeasurement ParagraphLayoutManager::measure(
2841
}
2942
}
3043

31-
return textLayoutManager_->measure(
32-
AttributedStringBox(attributedString),
33-
paragraphAttributes,
34-
layoutConstraints,
35-
hostTextStorage_);
44+
if (cacheLastTextMeasurement) {
45+
cachedTextMeasurement_ = textLayoutManager_->measure(
46+
AttributedStringBox(attributedString),
47+
paragraphAttributes,
48+
layoutConstraints,
49+
hostTextStorage_);
50+
51+
availableWidth_ = layoutConstraints.maximumSize.width;
52+
53+
return cachedTextMeasurement_;
54+
} else {
55+
return textLayoutManager_->measure(
56+
AttributedStringBox(attributedString),
57+
paragraphAttributes,
58+
layoutConstraints,
59+
hostTextStorage_);
60+
}
3661
}
3762

3863
LinesMeasurements ParagraphLayoutManager::measureLines(

packages/react-native/ReactCommon/react/renderer/components/text/ParagraphLayoutManager.h

+12
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,18 @@ class ParagraphLayoutManager {
5454
std::shared_ptr<TextLayoutManager const> mutable textLayoutManager_{};
5555
std::shared_ptr<void> mutable hostTextStorage_{};
5656

57+
/* The width Yoga set as maximum width.
58+
* Yoga sometimes calls measure twice with two
59+
* different maximum width. One if available space.
60+
* The other one is exact space needed for the string.
61+
* This happens when node is dirtied but its size is not affected.
62+
* To deal with this inefficiency, we cache `TextMeasurement` for each
63+
* `ParagraphShadowNode`. If Yoga tries to re-measure with available width
64+
* or exact width, we provide it with the cached value.
65+
*/
66+
Float mutable availableWidth_{};
67+
TextMeasurement mutable cachedTextMeasurement_{};
68+
5769
size_t mutable hash_{};
5870
};
5971
} // namespace facebook::react

packages/react-native/ReactCommon/react/renderer/core/CoreFeatures.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ bool CoreFeatures::enableMapBuffer = false;
1515
bool CoreFeatures::blockPaintForUseLayoutEffect = false;
1616
bool CoreFeatures::useNativeState = false;
1717
bool CoreFeatures::cacheNSTextStorage = false;
18+
bool CoreFeatures::cacheLastTextMeasurement = false;
1819

1920
} // namespace react
2021
} // namespace facebook

packages/react-native/ReactCommon/react/renderer/core/CoreFeatures.h

+5
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ class CoreFeatures {
3939
// creating it twice. Once when measuring text and once when rendering it.
4040
// This flag caches it inside ParagraphState.
4141
static bool cacheNSTextStorage;
42+
43+
// Yoga might measure multiple times the same Text with the same constraints
44+
// This flag enables a caching mechanism to avoid subsequents measurements
45+
// of the same Text with the same constrainst.
46+
static bool cacheLastTextMeasurement;
4247
};
4348

4449
} // namespace react

packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/android/react/renderer/textlayoutmanager/TextLayoutManager.cpp

+5-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <react/common/mapbuffer/JReadableMapBuffer.h>
1313
#include <react/jni/ReadableNativeMap.h>
1414
#include <react/renderer/attributedstring/conversions.h>
15+
#include <react/renderer/core/CoreFeatures.h>
1516
#include <react/renderer/core/conversions.h>
1617
#include <react/renderer/mapbuffer/MapBuffer.h>
1718
#include <react/renderer/mapbuffer/MapBufferBuilder.h>
@@ -146,7 +147,10 @@ Size measureAndroidComponentMapBuffer(
146147
TextLayoutManager::TextLayoutManager(
147148
const ContextContainer::Shared &contextContainer)
148149
: contextContainer_(contextContainer),
149-
measureCache_(kSimpleThreadSafeCacheSizeCap) {}
150+
measureCache_(
151+
CoreFeatures::cacheLastTextMeasurement
152+
? 8096
153+
: kSimpleThreadSafeCacheSizeCap) {}
150154

151155
void *TextLayoutManager::getNativeTextLayoutManager() const {
152156
return self_;

0 commit comments

Comments
 (0)