Repository navigation
Create one JNI global reference per prepared text layout on Android - #59592
Open
hryhoriiK97 wants to merge 1 commit into
Open
hryhoriiK97 wants to merge 1 commit into
hryhoriiK97 wants to merge 1 commit into
Conversation
Every text layout created several JNI global references: TextLayoutManager read FabricUIManager from the context container, which copies its global reference, in prepareLayout, measurePreparedLayout and the other measure paths, and PreparedTextLayout was itself a global reference, so returning a cached layout and storing it in a node's state each created another one. PreparedTextLayout now holds its reference in a shared_ptr, so copies share it, and TextLayoutManager looks FabricUIManager up once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
|
Great find! Let's split the FabricUIManager off from the PreparedTextLayout changes here. I've got some ideas around the FabricUIManager one (it should really be an alias_ref being returned from the context container) |
Visual regression3 visual difference(s) at ios
|
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.









Summary:
On Android, every text layout created several JNI global references, and creating one is not free: ART adds it to its global reference table under a lock, and with references being created and deleted all the time this showed up as 10% of the JS thread on a screen of 200 updating numbers.
Where they came from, per text layout:
TextLayoutManagerreadFabricUIManagerfrom the context container inprepareLayoutand again inmeasurePreparedLayout(and in the other measure paths).ContextContainer::atreturns the storedjni::global_refby value, so each read creates a new global reference and deletes it at the end of the scope.PreparedTextLayoutwas itself aSafeReleaseJniRef<jni::global_ref<JPreparedLayout>>, so every copy creates a global reference: the cache keeps one,prepareLayoutreturns a copy of the cached entry, andParagraphShadowNodecopies the measured layout intoParagraphState.With this change:
PreparedTextLayoutkeeps itsSafeReleaseJniRefin astd::shared_ptr, so copies share one global reference. It keepsget()andoperator bool, which is what its users call, soParagraphShadowNode,ParagraphStateand the measure code are unchanged. The JavaPreparedLayoutis treated as immutable (new react tags produce a new one throughreusePreparedLayoutWithNewReactTags), so sharing one reference between the owners is the same as each holding its own; the last owner releases it, still throughSafeReleaseJniRefso it can happen on any thread.TextLayoutManagerreadsFabricUIManagerfrom the context container once, in its constructor, and every JNI call it makes uses that reference. The context container owns theTextLayoutManager(or the component descriptor that created it), so the reference can't outlive theFabricUIManagerit points to.A new prepared layout now creates one global reference, as does a cached layout reused with new react tags; reusing a cached layout as is, or copying a layout into state, creates none. Besides the time, this keeps fewer global references alive (about one per distinct layout instead of one per text node per owner), further from ART's global reference limit on screens with a lot of text.
TextLayoutManager::PreparedTextLayoutchanges from an alias to a class in the Android C++ API snapshot.Changelog:
[ANDROID] [CHANGED] - Create one JNI global reference per prepared text layout, instead of several for every text layout
Test Plan:
node scripts/clang-format.js --checkreports no changes for the touched files.ReactAndroidFrameworksCxx.apiwas regenerated withscripts/cxx-api../gradlew :packages:rn-tester:android:app:assembleRelease -PreactNativeArchitectures=arm64-v8asucceeds with this change (in the checkout described below).prepareTextLayoutcalls;TextLayoutManageridentical tomainat 92629fd), without and with this change, installed alternately on a moto g57 power (Android 16). Playground screens where texts change every 100 ms; JS and main thread CPU time from/procover 20 s, divided by the number of text layouts:The main thread time per update is unchanged in all of them. The 200-number and remount screens ran six rounds and the other two three: the first three rounds used a build that looked
FabricUIManagerup lazily withstd::call_once(an earlier version of this change), the last three the final code, with matching results.In a simpleperf profile of the 200-number screen,
art::JNI::NewGlobalRefwent from 10.3% to 2.3% of the JS thread.<Text onPress>, wrapping) is pixel-identical between the two builds, and tapping its link firesonPresswith both.🤖 Generated with Claude Code