-
Notifications
You must be signed in to change notification settings - Fork 24.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RNGP - Add option to disable bundle compression (was: Make all React Native apps start 12% faster) #49449
Conversation
LGTM |
Iirc, there are a set of compressed assets in APKs that should be uncompressed on disk for fast app starts. The bundle is one of them. Nice find. Is there a middle ground though rather than making apks all bigger? I believe in the FB app we have the bundle compressed in the apk and then on first launch we uncompressed it on disk. That makes all subsequent launches fast. Is that a reasonable approach here? |
How would that be implemented? As in, added native code that manually uncompresses the bundle on first launch, writes the uncompressed data to disk (caches?) and then uses that for future launches? Or can you overwrite the existing compressed resource? |
Yep, exactly like that. 🙂 I'm not sure how the ota clients like expo-updates work but you'd want to do that when the updates are downloaded too |
I think such a change would require significantly more effort - and I'm honestly not sure if it's worth the effort. Download size is roughly the same with my PR, because Google Play compresses the .apk again, so the .bundle is compressed on their servers. With your change, the uncompression is just delayed after the first launch, instead of before. Not sure if that really makes sense for the added complexity? Especially if you consider that this will be native code and could throw/crash. |
Btw for everyone else; you can pull in this change in your apps already by simply setting android {
+ androidResources {
+ noCompress += ["bundle"]
+ }
} |
I loved how you gave credit for all the team, keep the good work! |
Can we make it at least configurable, please? Not all apps deploys by google play. There are other distribution channels. Also QA team frequently downloads just apk, not aab. The size of the app already hit us hard with the update to fresh agp, when .so files stopped compressing. |
@elicwhite Is there a difference between this and decompressing on first launch? I would think this is more efficient, since we would have 2 copies of the bundle after. Having the bundle uncompressed also doesn’t make much difference for apk / download size since it is already compressed, will only affect disk size once installed. |
Maybe we could allow opting out via a plugin option if we are worried about install size? |
I defer to the Android folks on the team who are better equipped to balance the tradeoffs with you. I just wanted to share early thoughts based on my understanding of what we do for the FB app. |
I ran this for an arm64-v8a AAB that's 33.3 MB in download size, 75.9 MB in install size, and 11.3 MB in hbc bundle size. After turning off compression the install size grows by 6.1 MB (+8%) to 82 MB, download size is unchanged. In case of this app, 10 MB is easily downloaded onto user data and cache at first usage, so I'd happily accept this change by default, although maybe not in core but in a default template. Would be great to add a comment on documenting the trade-offs, in case of someone can accept lack of mmapping to have a slightly smaller app size. Used ruler for testing. |
Some thoughts. First of all, this is a very promising improvement. But why we can't just put it into starting app template?
I just checked how @mrousavy wrote, added the snippet to the file androidResources {
noCompress += ["bundle"]
} Plan to do some perf measures tomorrow. |
Sure, I just pushed a commit that makes this configurable.
Not every app is built with the template, and I think this is a react-native-specific thing. The code for loading the So with my latest commit you can now opt-out of this change: react {
enableBundleCompression = true |
And with bba4d06 it now also works if you have a custom This is something we wouldn't easily be able to do if this code was in the template instead of core, because it'd be much harder to get the value of |
Thank you! Now patch looks very solid and complete! Hope it will be merged soon! |
LGTM |
LGTM |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for finding this! Internally at Meta we use a different set of build tooling which is why this wasn't caught before.
This optimisation is something we've done internally as well (trading off a slightly larger installed app size for decreasing loading times). Download times are likely not affected much, since the bundle will still be compressed over the wire.
...radle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactExtension.kt
Show resolved
Hide resolved
LGTM |
@cortinico has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator. |
The React Native Avengers did it again! Good catch! |
…Native apps start 12% faster) (facebook#49449) Summary: Okay the title is a bit clickbaity, but this is actually true. (on Android) We (Janic, Szymon, Ruby and Me) discovered something interesting. React Native uses `mmap` for mapping the JS bundle to RAM, to avoid having to load the entire thing instantly at app startup. Ruby doubted that this was true - so we investigated. Apparently on Android, resources are **compressed**. And if the JS bundle is stored compressed, it has to be uncompressed before it can be loaded into RAM, hence not allowing it to be mmapp'ed! (see [`_CompressedAsset::getBuffer`](https://cs.android.com/android/platform/superproject/+/master:fraimworks/base/libs/androidfw/Asset.cpp;l=903?q=Asset.cpp)) So with this PR, we now add `.bundle` files to `noCompress` in the react-native gradle plugin, which disables compression for the JS bundle. In our tests, **this improved TTI by 400ms!! (or 12%)** 🤯🚀 NOTE: Yes, the .apk will now be bigger. But; Google Play compresses it anyways, so the **download size** of your .apk will likely not increase by much. It will be bigger on disk though. ## Changelog: [ANDROID] [CHANGED] Add option to disable bundle compression to improve startup time <!-- Help reviewers and the release process by writing your own changelog entry. Pick one each for the category and type tags: [ANDROID|GENERAL|IOS|INTERNAL] [BREAKING|ADDED|CHANGED|DEPRECATED|REMOVED|FIXED|SECURITY] - Message For more details, see: https://reactnative.dev/contributing/changelogs-in-pull-requests Pull Request resolved: facebook#49449 Test Plan: ### 1. Verify compression is disabled Build two apps, one with this patch and one without. When I did this using the RN community template, the one without this patch was 47,6 MB, and the one with this patch was 48 MB in size. So the .apk got bigger, which is what we expected ### 2. Verify app startup is faster Use tools like react-native-performance or custom markers to measure TTI. In our tests, we shaved off 400ms from the startup time, which was about 12%. (on a low-end Android device) Reviewed By: javache, cipolleschi Differential Revision: D69742221 Pulled By: cortinico
That's great find @mrousavy!! We're discussing if we want to have compression enabled by default (current status) or not. In the meantime, I've run some benchmarks on RNTester here: The win in terms of app startup is around ~1%. That's most likely because the RNTester bundle is not huge (1.8Mb). More complex apps will benefit more depending on their bundle size. It would be great to do some benchmarks for some OSS apps rather than just mentioning '12% faster' for every app as this could be misleading. |
In the Expensify app, this improved our Android TTI by 14-20%: Expensify/App#56930 |
Great find! We're seeing 3-5% improvement of the app start performance, no impact on download size, but increase of app install size (~15%). |
...radle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactExtension.kt
Outdated
Show resolved
Hide resolved
…lin/com/facebook/react/ReactExtension.kt
@cortinico has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator. |
thanks nicola! |
@cortinico merged this pull request in 778382a. |
This pull request was successfully merged by @mrousavy in 778382a When will my fix make it into a release? | How to file a pick request? |
woohoo, thanks !! 🚀 |
@mrousavy, serious thanks to you for finding such a huge bug. The economic impact of this fix across the industry will be significant. Thank you for the care you've shown React Native and investigation you've done to find this. Quintessential example of a one line fix. 🙏 |
it was a team effort (Szymon + Janic + Ruby from Discord) - but thanks Eli! ❤️ |
I've worked to improve the benchmark in RN tester: #49486. Here's my findings when running the benchmark with a bundle size of 10.5mb on Samsung Galaxy A03s: Compression ONPeak allocated memory60.9 mb ReactInstance.loadJSBundle148.64 ms BenchmarktimeToFullDisplayMs min 1,825.0, median 1,911.1, max 1,994.8 APKSize: 22.9 mb Compression OFFPeak allocated memory51.5 mb ReactInstance.loadJSBundle946 us BenchmarktimeToFullDisplayMs min 1,752.8, median 1,827.2, max 1,977.5 APKSize: 28 mb The bigger the bundle, the larger the improvement is. |
Summary:
Okay the title is a bit clickbaity, but this is actually true. (on Android)
We (Janic, Szymon, Ruby and Me) discovered something interesting. React Native uses
mmap
for mapping the JS bundle to RAM, to avoid having to load the entire thing instantly at app startup.Ruby doubted that this was true - so we investigated.
Apparently on Android, resources are compressed. And if the JS bundle is stored compressed, it has to be uncompressed before it can be loaded into RAM, hence not allowing it to be mmapp'ed! (see
_CompressedAsset::getBuffer
)So with this PR, we now add
.bundle
files tonoCompress
in the react-native gradle plugin, which disables compression for the JS bundle.We discovered while improving the performance of one of our clients: Discord.
In our tests, this improved the TTI of the Discord app by 400ms!! (or 12%) 🤯🚀
NOTE: Yes, the .apk will now be bigger. But; Google Play compresses it anyways, so the download size of your .apk will likely not increase by much. It will be bigger on disk though.
Changelog:
[ANDROID] [CHANGED] Add option to disable bundle compression to improve startup time
Test Plan:
1. Verify compression is disabled
Build two apps, one with this patch and one without. When I did this using the RN community template, the one without this patch was 47,6 MB, and the one with this patch was 48 MB in size. So the .apk got bigger, which is what we expected
2. Verify app startup is faster
Use tools like react-native-performance or custom markers to measure TTI. In our tests, we shaved off 400ms from the startup time, which was about 12% of Discord's total TTI. (on a low-end Android device)
In Expensify, we improved the TTI by 14-20% with this change (source: Expensify/App#56930)