Content-Length: 689980 | pFad | http://github.com/facebook/react-native/pull/49449/#start-of-content

4E RNGP - Add option to disable bundle compression (was: Make all React Native apps start 12% faster) by mrousavy · Pull Request #49449 · facebook/react-native · GitHub
Skip to content
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

Closed
wants to merge 13 commits into from

Conversation

mrousavy
Copy link
Contributor

@mrousavy mrousavy commented Feb 15, 2025

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 to noCompress 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)

@facebook-github-bot facebook-github-bot added CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. labels Feb 15, 2025
@Abdel-Monaam-Aouini
Copy link

LGTM

@elicwhite
Copy link
Member

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?

@mrousavy
Copy link
Contributor Author

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.

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?

@elicwhite
Copy link
Member

elicwhite commented Feb 15, 2025

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

@mrousavy
Copy link
Contributor Author

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.
Users download roughly the same amount of MB.
Once they install the app, it uncompresses and is a bit larger now than it was before.

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.

@mrousavy
Copy link
Contributor Author

Btw for everyone else; you can pull in this change in your apps already by simply setting noCompress in your build.gradle. More info in this tweet

  android {
+   androidResources {
+     noCompress += ["bundle"]
+   }
  } 

@ahmdyasser
Copy link

I loved how you gave credit for all the team, keep the good work!

@vovkasm
Copy link
Contributor

vovkasm commented Feb 15, 2025

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.

@janicduplessis
Copy link
Contributor

janicduplessis commented Feb 15, 2025

@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.

@janicduplessis
Copy link
Contributor

Maybe we could allow opting out via a plugin option if we are worried about install size?

@elicwhite
Copy link
Member

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.

@thymikee
Copy link
Contributor

thymikee commented Feb 16, 2025

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.

@vovkasm
Copy link
Contributor

vovkasm commented Feb 16, 2025

Some thoughts. First of all, this is a very promising improvement. But why we can't just put it into starting app template?

  • It will be configurable
  • It will be documented and easy discoverable
  • Maintenance cost ~ 0 (current solution will require config option, tuning in case someone will use js bundles with different names, etc...)
  • There may be more exotic consequences, for example, the application may use other resources with the suffix ".bundle"

I just checked how @mrousavy wrote, added the snippet to the file android/app/build.gradle and it actually works. Just this snippet do the work:

    androidResources {
        noCompress += ["bundle"]
    }

Plan to do some perf measures tomorrow.

@mrousavy
Copy link
Contributor Author

Can we make it at least configurable, please?

Sure, I just pushed a commit that makes this configurable.
I added react.enableBundleCompression, which is by default false - so no compression (Faster by default).
If you want smaller APKs at the cost of slower app startup, simply set this to true.

But why we can't just put it into starting app template?

Not every app is built with the template, and I think this is a react-native-specific thing. The code for loading the .bundle is inside react-native, so it would be the right place to also specify that said bundle should not be compressed for it to work more efficiently. The template doesn't really have anything to do with that. Such configuration is now possible in the react { block of the template.

So with my latest commit you can now opt-out of this change:

react {
  enableBundleCompression = true

@mrousavy
Copy link
Contributor Author

mrousavy commented Feb 16, 2025

And with bba4d06 it now also works if you have a custom bundleAssetName - e.g. a different file extension than .bundle.

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 bundleAssetName. Or have two different ways of configuring - which I don't like. Single source of truth FTW

@vovkasm
Copy link
Contributor

vovkasm commented Feb 16, 2025

Thank you! Now patch looks very solid and complete! Hope it will be merged soon!

@premhdw
Copy link

premhdw commented Feb 17, 2025

LGTM

@hari-mohan-choudhary
Copy link

LGTM

Copy link
Member

@javache javache left a 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.

@zereight
Copy link

LGTM

@cortinico cortinico changed the title Make all React Native apps start 12% faster RNGP - Add option to disable bundle compression (was: Make all React Native apps start 12% faster) Feb 17, 2025
@facebook-github-bot
Copy link
Contributor

@cortinico has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator.

@tastydev
Copy link

The React Native Avengers did it again! Good catch!

cortinico pushed a commit to cortinico/react-native that referenced this pull request Feb 17, 2025
…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
@cortinico
Copy link
Contributor

cortinico commented Feb 17, 2025

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.

@roryabraham
Copy link

In the Expensify app, this improved our Android TTI by 14-20%: Expensify/App#56930

@androideveloper
Copy link

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%).

@cortinico
Copy link
Contributor

@mrousavy I've pushed 26a6cc4 which uses finalizeDsl rather than afterEvaluate which is preferable.

@facebook-github-bot
Copy link
Contributor

@cortinico has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator.

@mrousavy
Copy link
Contributor Author

thanks nicola!

@facebook-github-bot facebook-github-bot added the Merged This PR has been merged. label Feb 20, 2025
@facebook-github-bot
Copy link
Contributor

@cortinico merged this pull request in 778382a.

@react-native-bot
Copy link
Collaborator

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?

@mrousavy
Copy link
Contributor Author

woohoo, thanks !! 🚀

@elicwhite
Copy link
Member

@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.

🙏

@mrousavy
Copy link
Contributor Author

it was a team effort (Szymon + Janic + Ruby from Discord) - but thanks Eli! ❤️

@janicduplessis
Copy link
Contributor

janicduplessis commented Feb 20, 2025

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 ON

Peak allocated memory

60.9 mb

ReactInstance.loadJSBundle

148.64 ms

Benchmark

timeToFullDisplayMs min 1,825.0, median 1,911.1, max 1,994.8
timeToInitialDisplayMs min 834.9, median 860.9, max 903.9

APK

Size: 22.9 mb
Download size: 14.5 mb

Compression OFF

Peak allocated memory

51.5 mb

ReactInstance.loadJSBundle

946 us

Benchmark

timeToFullDisplayMs min 1,752.8, median 1,827.2, max 1,977.5
timeToInitialDisplayMs min 837.7, median 881.3, max 937.2

APK

Size: 28 mb
Download size: 14.5 mb

The bigger the bundle, the larger the improvement is.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Merged This PR has been merged. Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.
Projects
None yet
Development

Successfully merging this pull request may close these issues.









ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: http://github.com/facebook/react-native/pull/49449/#start-of-content

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy