Refactor caching of Spannable objects instide TextLayoutManager

Summary:
This diff optimizes the caching of Spannable objects managed by the TextLayoutManager class.
Previously, these objects were cached using unsing a String representation of the RedableMap (creating this string adds a non trivial cost), this diff improves the caching performance relying on the equals / hashcode methods of the ReadableNativeMap class

I created a MC just to have a killswitch

Motivation: I was analysing another bug and I found this non performant code

changelog: [internal] internal

Reviewed By: shergin

Differential Revision: D23429365

fbshipit-source-id: 59e5ad0b1b95da992ac393aecfe029da68a8df97
This commit is contained in:
David Vacca
2020-09-01 17:09:27 -07:00
committed by Facebook GitHub Bot
parent 045d8fe8c3
commit 7d6d5daa2b
3 changed files with 36 additions and 8 deletions
@@ -71,4 +71,7 @@ public class ReactFeatureFlags {
/** Use experimental SetState retry mechanism in view? */
public static boolean enableExperimentalStateUpdateRetry = false;
/** Enable caching of Spannable objects using equality of ReadableNativeMaps */
public static boolean enableSpannableCacheByReadableNativeMapEquality = true;
}
@@ -22,6 +22,7 @@ rn_android_library(
react_native_dep("third-party/java/jsr-305:jsr-305"),
react_native_target("java/com/facebook/react/bridge:bridge"),
react_native_target("java/com/facebook/react/common:common"),
react_native_target("java/com/facebook/react/config:config"),
react_native_target("java/com/facebook/react/module/annotations:annotations"),
react_native_target("java/com/facebook/react/uimanager:uimanager"),
react_native_target("java/com/facebook/react/uimanager/annotations:annotations"),
@@ -26,6 +26,8 @@ import androidx.annotation.Nullable;
import com.facebook.common.logging.FLog;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableNativeMap;
import com.facebook.react.config.ReactFeatureFlags;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.react.uimanager.ReactStylesDiffMap;
import com.facebook.react.uimanager.ViewProps;
@@ -61,6 +63,8 @@ public class TextLayoutManager {
private static final String MAXIMUM_NUMBER_OF_LINES_KEY = "maximumNumberOfLines";
private static final LruCache<String, Spannable> sSpannableCache =
new LruCache<>(spannableCacheSize);
private static final LruCache<ReadableNativeMap, Spannable> sSpannableCacheV2 =
new LruCache<>(spannableCacheSize);
private static final ConcurrentHashMap<Integer, Spannable> sTagToSpannableCache =
new ConcurrentHashMap<>();
@@ -179,20 +183,40 @@ public class TextLayoutManager {
@Nullable ReactTextViewManagerCallback reactTextViewManagerCallback) {
Spannable preparedSpannableText;
String attributedStringPayload = attributedString.toString();
synchronized (sSpannableCacheLock) {
preparedSpannableText = sSpannableCache.get(attributedStringPayload);
// TODO: T31905686 implement proper equality of attributedStrings
if (preparedSpannableText != null) {
return preparedSpannableText;
String attributedStringPayload = "";
boolean cacheByReadableNativeMap =
ReactFeatureFlags.enableSpannableCacheByReadableNativeMapEquality;
// TODO: T74600554 Cleanup this experiment once positive impact is confirmed in production
if (cacheByReadableNativeMap) {
synchronized (sSpannableCacheLock) {
preparedSpannableText = sSpannableCacheV2.get((ReadableNativeMap) attributedString);
if (preparedSpannableText != null) {
return preparedSpannableText;
}
}
} else {
attributedStringPayload = attributedString.toString();
synchronized (sSpannableCacheLock) {
preparedSpannableText = sSpannableCache.get(attributedStringPayload);
if (preparedSpannableText != null) {
return preparedSpannableText;
}
}
}
preparedSpannableText =
createSpannableFromAttributedString(
context, attributedString, reactTextViewManagerCallback);
synchronized (sSpannableCacheLock) {
sSpannableCache.put(attributedStringPayload, preparedSpannableText);
if (cacheByReadableNativeMap) {
synchronized (sSpannableCacheLock) {
sSpannableCacheV2.put((ReadableNativeMap) attributedString, preparedSpannableText);
}
} else {
synchronized (sSpannableCacheLock) {
sSpannableCache.put(attributedStringPayload, preparedSpannableText);
}
}
return preparedSpannableText;
}