Skip to content

Commit

Permalink
Improve caching of Text per shadow Node
Browse files Browse the repository at this point in the history
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
  • Loading branch information
mdvacca authored and facebook-github-bot committed Mar 20, 2023
1 parent a999f0d commit 8c01b56
Show file tree
Hide file tree
Showing 7 changed files with 59 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ public class ReactFeatureFlags {
/** Feature Flag to enable the pending event queue in fabric before mounting views */
public static boolean enableFabricPendingEventQueue = false;

/** Feature Flag to enable caching mechanism of text measurement at shadow node level */
public static boolean enableTextMeasureCachePerShadowNode = false;

/**
* Feature flag that controls how turbo modules are exposed to JS
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,9 @@ void Binding::installFabricUIManager(
"CalculateTransformedFramesEnabled",
getFeatureFlagValue("calculateTransformedFramesEnabled"));

CoreFeatures::cacheLastTextMeasurement =
getFeatureFlagValue("enableTextMeasureCachePerShadowNode");

// Props setter pattern feature
CoreFeatures::enablePropIteratorSetter =
getFeatureFlagValue("enableCppPropsIteratorSetter");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,19 @@ TextMeasurement ParagraphLayoutManager::measure(
AttributedString const &attributedString,
ParagraphAttributes const &paragraphAttributes,
LayoutConstraints layoutConstraints) const {
bool cacheLastTextMeasurement = CoreFeatures::cacheLastTextMeasurement;
if (cacheLastTextMeasurement &&
(layoutConstraints.maximumSize.width == availableWidth_ ||
layoutConstraints.maximumSize.width ==
cachedTextMeasurement_.size.width)) {
/* Yoga has requested measurement for this size before. Let's use cached
* value. `TextLayoutManager` might not have cached this because it could be
* using different width to generate cache key. This happens because Yoga
* switches between available width and exact width but since we already
* know exact width, it is wasteful to calculate it again.
*/
return cachedTextMeasurement_;
}
if (CoreFeatures::cacheNSTextStorage) {
size_t newHash = folly::hash::hash_combine(
0,
Expand All @@ -28,11 +41,23 @@ TextMeasurement ParagraphLayoutManager::measure(
}
}

return textLayoutManager_->measure(
AttributedStringBox(attributedString),
paragraphAttributes,
layoutConstraints,
hostTextStorage_);
if (cacheLastTextMeasurement) {
cachedTextMeasurement_ = textLayoutManager_->measure(
AttributedStringBox(attributedString),
paragraphAttributes,
layoutConstraints,
hostTextStorage_);

availableWidth_ = layoutConstraints.maximumSize.width;

return cachedTextMeasurement_;
} else {
return textLayoutManager_->measure(
AttributedStringBox(attributedString),
paragraphAttributes,
layoutConstraints,
hostTextStorage_);
}
}

LinesMeasurements ParagraphLayoutManager::measureLines(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,18 @@ class ParagraphLayoutManager {
std::shared_ptr<TextLayoutManager const> mutable textLayoutManager_{};
std::shared_ptr<void> mutable hostTextStorage_{};

/* The width Yoga set as maximum width.
* Yoga sometimes calls measure twice with two
* different maximum width. One if available space.
* The other one is exact space needed for the string.
* This happens when node is dirtied but its size is not affected.
* To deal with this inefficiency, we cache `TextMeasurement` for each
* `ParagraphShadowNode`. If Yoga tries to re-measure with available width
* or exact width, we provide it with the cached value.
*/
Float mutable availableWidth_{};
TextMeasurement mutable cachedTextMeasurement_{};

size_t mutable hash_{};
};
} // namespace facebook::react
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ bool CoreFeatures::enableMapBuffer = false;
bool CoreFeatures::blockPaintForUseLayoutEffect = false;
bool CoreFeatures::useNativeState = false;
bool CoreFeatures::cacheNSTextStorage = false;
bool CoreFeatures::cacheLastTextMeasurement = false;

} // namespace react
} // namespace facebook
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ class CoreFeatures {
// creating it twice. Once when measuring text and once when rendering it.
// This flag caches it inside ParagraphState.
static bool cacheNSTextStorage;

// Yoga might measure multiple times the same Text with the same constraints
// This flag enables a caching mechanism to avoid subsequents measurements
// of the same Text with the same constrainst.
static bool cacheLastTextMeasurement;
};

} // namespace react
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <react/common/mapbuffer/JReadableMapBuffer.h>
#include <react/jni/ReadableNativeMap.h>
#include <react/renderer/attributedstring/conversions.h>
#include <react/renderer/core/CoreFeatures.h>
#include <react/renderer/core/conversions.h>
#include <react/renderer/mapbuffer/MapBuffer.h>
#include <react/renderer/mapbuffer/MapBufferBuilder.h>
Expand Down Expand Up @@ -146,7 +147,10 @@ Size measureAndroidComponentMapBuffer(
TextLayoutManager::TextLayoutManager(
const ContextContainer::Shared &contextContainer)
: contextContainer_(contextContainer),
measureCache_(kSimpleThreadSafeCacheSizeCap) {}
measureCache_(
CoreFeatures::cacheLastTextMeasurement
? 8096
: kSimpleThreadSafeCacheSizeCap) {}

void *TextLayoutManager::getNativeTextLayoutManager() const {
return self_;
Expand Down

0 comments on commit 8c01b56

Please sign in to comment.