Repository navigation
Conversation
Summary: react-android ships no Baseline Profile, so after every install or update the Java/Kotlin that React Native runs to show the first screen (host setup, Fabric mounting, view managers, Yoga) is interpreted and JIT-compiled until background dexopt or Play's cloud profiles catch up. AndroidX ships a baseline-prof.txt in its AARs for this reason. This adds ReactAndroid/src/main/baseline-prof.txt, which AGP packages into the AAR and merges into every app's profile. It was collected from a cold start of RNTester and scrolling its component list, and filtered to the classes that ship in ReactAndroid. Every method rule is marked HSP, because Android 14 and older only compile hot methods at install. To regenerate it: - BaselineProfileGenerator, in RNTester's benchmark module, collects the profile. - scripts/update-android-baseline-profile.js filters and sorts it. With --check, it reports rules that no longer match ReactAndroid's classes. - -Preact.internal.disableMinifyInReleaseBuilds=true turns off R8 for RNTester release builds. Macrobenchmark goes from 1.3.3 to 1.5.0, because 1.3.3 can't parse `pm dump-profiles` output on Android 16. On a OnePlus CPH2667 (Android 16), time to full display drops by 8.7% on RNTester (R8 on) and by 12.5% on a first screen of about 750 views. Time to initial display doesn't change. Changelog: [Android][Added] - Ship a Baseline Profile with `react-android` so that React Native's startup code is compiled ahead of time Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
shubh73
force-pushed
the
android-baseline-profile-independent
branch
from
October 18, 2026 18:49
f7da075 to
d542945
Compare
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:
react-androiddoesn't ship a Baseline Profile, so after every install or update, the Java/Kotlin that React Native runs to show the first screen (host setup, Fabric mounting, view managers and their prop setters, Yoga) is interpreted and JIT-compiled on every cold start until background dexopt catches up. AndroidX ships abaseline-prof.txtin most of its AARs for this reason.This adds
ReactAndroid/src/main/baseline-prof.txt. AGP packages it into the AAR and merges it into every app's profile, so apps don't need to change anything. Play compiles it at install; other installs pick it up through ProfileInstaller, which apps already get from AndroidX.It has 724 classes and 2,544 methods, collected from a cold start of RNTester and scrolling its component list, filtered to ReactAndroid's classes. Every method is marked
HSP, like AndroidX does, because Android 14 and older (and low-RAM devices) only compile methods marked hot at install. About 230 rules are for lambdas D8 generates from ReactAndroid's classes. R8 recreates those for minified apps, but D8 doesn't for non-minified ones (the template's default), so they stay.What it improves is time to full display, i.e. when React Native's first content is on screen. Time to initial display doesn't move, since what runs before the first frame is mostly native and framework code. The gain grows with how much UI the first screen mounts: about 2% on the template's hello world (within noise), 7–9% on RNTester and 12–13% on a feed-like first screen.
On Play, cloud profiles eventually cover the same code, but only after early users of each release have run it, only for Play installs on Android 9+, and Google notes they "only scale well for apps that have a sufficiently large user base". This covers Play installs and updates from the first launch, and other installs once background dexopt has run.
To keep the profile maintainable:
BaselineProfileGenerator(next toRNTesterStartupBenchmark) collects it, and fails rather than collecting a partial journey.scripts/update-android-baseline-profile.jskeeps ReactAndroid's rules, marks themHSPand sorts them so updates diff cleanly.--checkfails if a rule names a class or method that no longer exists, without needing a device. I'm happy to wire it into CI in a follow-up.-Preact.internal.disableMinifyInReleaseBuilds=trueturns off R8 for RNTester so the collected names match ReactAndroid. The default is unchanged.pm dump-profileson Android 16. Only the benchmark module uses it.Rules that stop matching are ignored, so a stale profile only loses coverage. I didn't use the Baseline Profile Gradle plugin because it keeps the flags ART recorded, and in the collected profile only 235 of about 10,000 methods are marked hot, so most of it wouldn't be compiled on Android 14 and older. It would also add a plugin to ReactAndroid's build.
I also tried a wider journey that typed into a TextInput, pressed Pressables and opened the Image and ActivityIndicator examples. It added 866 methods and made startup about 1.6% slower than this profile, even on a screen that uses those components, so I kept the journey to startup.
Changelog:
[ANDROID] [ADDED] - Ship a Baseline Profile with
react-androidso that React Native's startup code is compiled ahead of timeTest Plan:
OnePlus CPH2667, Android 16, release builds installed the way Play installs them (
adb install-multiple app.apk app.dm, with the.dmfromprofgen extractProfile), checked withpm art dump(speed-profile,install-dm). "Without" still has AndroidX's profiles. Each comparison rotates the builds across package IDs in a Latin square and launches them interleaved in random order; confidence intervals are 95% bootstrap intervals. Time to full display isreportFullyDrawn()on React Native's first content, time to initial display isam start -W.Values are median time to full display. Hello world is pooled from two runs on different days and is within noise.
Time to initial display doesn't change: −1.5% (−3.9% to +0.8%) on RNTester and −0.1% (−2.5% to +2.4%) on the 750-view template. A repeat of the RNTester run on a warmer phone gave −6.9% (−9.0% to −4.8%).
RNTesterStartupBenchmark, 3 × 10 iterations each way (withstartActivityAndWait()swapped foram start -Wlocally, as it can't confirm launches on ColorOS):Time to initial display is within noise here, consistent with the interleaved runs above. Macrobenchmark traces every iteration and runs the builds back to back, so I'd trust the interleaved numbers more.
Cost:
react-androidrelease AAR: 48,486,181 → 48,517,339 bytes. The text file is 336 KB in the npm package (30 KB gzipped).assets/dexopt/baseline.prof: 1.8 KB → 4.7 KB.cmd package compile -m speed-profile -f): about 1.03 s → 1.13 sdumpsys meminfo, median of 5): total PSS unchanged within noise (~101 MB),.art mmap1.7 → 2.4 MBbase.odex617 KB → 1.75 MB,base.art100 KB → 316 KB. The APK size doesn't change.To check
HSPon older Android, I installed the 750-view template the same way on an Android 14 emulator (API 34, arm64) and counted compiled methods withoatdump: 384 React Native methods are compiled at install without the profile, and 2,692 with it. With the same rules markedSPinstead ofHSP, it stays at 384, which is why every rule isHSP.Regenerating with the steps in
BaselineProfileGenerator's KDoc reproduces the committed file exactly.node scripts/update-android-baseline-profile.js --checkpasses (3,037 rules checked; D8-generated names are skipped), and fails as expected with a bogus class and method appended.node scripts/format-kotlin.js --check,prettierandeslintpass.I could only measure startup on one phone, so I haven't been able to time this on Android 14 or older, on a low-RAM device or on other OEMs, and I haven't run CI's
build_android. If you'd like startup numbers from one of those before merging, I'd love for someone with such a device to give it a quick try.🤖 Generated with Claude Code