Mobile devices are generally more resource constrained than laptops or desktops. Optimizing Chrome’s resource usage is critical to give mobile users a faster Chrome experience. As we’ve added features to Chrome on Android, the amount of Java code packaged in the app has continued to grow. In this The Fast and the Curious post we show how our team improved the speed and memory usage of Chrome on Android with Isolated Splits. With these improvements, Chrome on Android now uses 5-7% less memory, and starts and loads pages even faster than before.
Ideally, we would load the smallest chunk of Java necessary for a process to run. We can get close to this by using Android App Bundles and splitting code into feature modules. Feature modules allow splitting code, resources, and assets into distinct APKs installed alongside the base APK, either on-demand or during app install.
Now, it seems like we have exactly what we want: a feature module could be created for the browser process code, which could be loaded when needed. However, this is not how Android loads feature modules. By default, all installed feature modules are loaded on startup. For an app with a base module and three feature modules “a”, “b”, and “c”, this gives us an Android Context with a ClassLoader that looks something like this:
Having a small minimum set of installed modules that are all immediately loaded at startup is beneficial in some situations. For example, if an app has a large feature that is needed only for a subset of users, the app could avoid installing it entirely for users who don’t need it. However, for more commonly used features, having to download a feature at runtime can introduce user friction — for example, additional latency or challenges if mobile data is unavailable. Ideally we’d be able to have all of our standard modules installed ahead of time, but loaded only when they’re actually needed.
In Chrome’s case, the small amount of code needed in the renderer and GPU processes can be kept in the base module, and the browser code and other expensive features can be split into feature modules to be loaded when needed. Using this method, we were able to reduce the .dex size loaded in child processes by 75% to ~2.5MB, making them start faster and use less memory.
This architecture also enabled optimizations for the browser process. We were able to improve startup time by preloading the majority of the browser process code on a background thread while the Application initializes leading to a 7.6% faster load time. By the time an Activity or other component which needed the browser code was launched, it would already be loaded. By optimizing how features are allocated into feature modules, features can be loaded on-demand which saves the memory and loading cost until the feature is used.
- Median total memory usage improved by 5.2%
- Median renderer process memory usage improved by 7.9%
- Median GPU process memory usage improved by 7.6%
- Median browser process memory usage improved by 1.2%
- 95th percentile startup time improved by 7.6%
- 95th percentile page load speed improved by 2.3%
- Large improvements in both browser crash rate and renderer hang rate
Posted by Clark Duvall, Chrome Software Engineer
Data source for all statistics: Real-world data anonymously aggregated from Chrome clients.