This document explains the overhead of each kind of binary size, where "overhead" is the performance cost of existing (e.g. does not include the overhead of loading an image, just the overhead of an image existing). This document focuses on Android, but several types of size are the same on other platforms.
*** note See optimization_advice.md for ideas on how to reduce size.
[TOC]
- Chrome's per-commit size is tracked in chromeperf:
- For Android, look for "resource_sizes".
- Googlers, see also go/chromeapksizes, and go/chromemilestonesizes.
- For other platforms: look for "sizes".
- For Android, look for "resource_sizes".
- As of 2019, Chrome for Android (arm32) grows by about 100kb per week.
- To get a feeling for how large individual features are, open a
milestone size breakdown and group by "Component" (Googlers only).
- For non-googlers, run
//tools/binary_size/supersize archive
on a release build to create a.size
file, and upload it to the viewer.
- For non-googlers, run
- Takes disk space away from users.
- Much less so than Chrome's cache, but this is space that cannot be cleared, and on Android is displayed prominently in the Play Store and in system settings.
- Takes disk space on system images for Android and Chrome OS.
- It routinely happens that system images fill up, and all components need to do their part to fit into the space.
- Binary size is a "proxy metric" for other performance metrics.
- It's a metric that is trivial to measure, and correlates to RAM usage, start-up speed etc. (there are nuances, which we try to capture in this doc).
- Binary size affects users' perception of Chrome's performance.
- E.g. Large app size leads users to think Chrome is "bloated".
- Binary size is much easier to stay on top of than it is to retroactively fix.
Googlers - see go/chromebinarysizematters for more links to studies.
Chrome is currently not under any hard growth limits, but to mitigate unnecessary bloat of this shared resource, everyone should ensure that reasonable diligence is taken to minimize growth.
- "Reasonable diligence" requires judgement and varies depending on the change.
A rough guideline:
- For small (<50kb) increases: spend a day trying to reduce (unless it's obviously not possible).
- For larger increases: Try to understand and document why the change requires so much size.
- If you are unsure, reach out on binary-size@ for advice.
- The android-binary-size trybot will alert for single commits that increase
binary size on 32-bit Android by more than 16kb.
- While such increases are often fine, this helps prevent unexpected increases.
- It typically takes about a week of engineering time to reduce Android's binary size by 50kb.
Android WebView is used by the majority of Android apps, so overhead (other than clean memory) introduced into WebView has a multiplicative effect on the OS. See android_build_instructions.md for how packaging of Chrome / WebView changes by OS version.
Chrome ships as an Android App Bundle, and consists of several APK splits.
- Loaded on start-up by every process.
- Keeping its dex size minimal is crucial, since it has both RAM and start-up overhead per-renderer.
- Loaded on start-up by the browser process.
- Important to keep dex size small in order to have Chrome start-up quickly, and to minimize our baseline memory requirement.
- Since they are loaded on-demand, binary size matters proportionally to the number of users that use the module.
- These are feature splits that are not even downloaded until needed. Binary size matters proportionally to the number of users that use the module.
Here we define some terms used to describe overhead.
Clean Memory:
- Backed by a file on disk, and mapped to virtual memory via
mmap()
. - Shared between processes.
- Paged into RAM on-demand by the OS.
- Fast when disk cache is hot, and slower when disk cache is cold.
Dirty Memory:
- Not backed by a file, and thus cannot be safely paged out (except to zram)
Machine instructions.
- RAM: Clean Memory, with good locality thanks to PGO.
- Start-up: No effect beyond page faults.
String literals, global constants, etc.
- RAM: Clean Memory, with poor locality (most is paged in after a short amount of time)
- Start-up: No effect beyond page faults.
Read-only data that must be adjusted based on the base load address of the executable.
- RAM: Dirty-ish Memory. It's complicated. Refer to [native_relocations.md].
- Start-up: Some overhead. Again, read the linked doc.
Global non-const variables.
- RAM: Dirty Memory
- Start-up: The entire section is loaded before any code is run.
UI Images, chrome:// pages, UI strings, etc.
- RAM: Clean Memory
- Start-up: Platform-dependent. None on Android. Page faults on desktop.
- Locality improved using an orderfile on some platforms.
Java bytecode in the DEX file format, stored in classes.dex
, classes2.dex
,
... When Android installs Chrome, this bytecode is turned into machine code and
stored as .odex
& .vdex
files. The size of these files depends on the OS
version and dex compilation profile. Generally, they are 1x the size of
uncompressed .dex
on Android Go, and 4x the size of uncompressed .dex
on
other devices.
- RAM: Mostly Clean Memory, but Some Dirty Memory as well.
- E.g.: The number of method declarations (referred to as "method count") directly corresponds to dirty RAM, where 1 entry = 4 bytes (on arm32).
- Start-up: Impact proportional to overall size. Mitigated by packaging code into feature splits when possible.
When changing .java code
, the change in method count is
shown in code reviews. The count is the number of method
references added/removed after optimization. Which methods are added/removed
can be seen in the "APK Breakdown" link by checking "Method Count Mode".
Method count is a useful thing to look at because:
- Each method reference has overhead within the dex file format, and for smallish methods, contributes more to binary size than its corresponding executable code.
- Method references that survive R8 optimization show how optimizable your abstractions are. Try to use low-overhead (or zero-overhead) abstractions. If you find that you're adding a lot of methods, you should see whether a different abstraction would result in fewer methods.
All files within res/
, as well as individual entries with res/values
files
contribute to this file. It consists of a string table, plus one 2D array
for each resource type (strings, drawable, etc). The overhead (not included
actual data) for each table is: # [unique configs] * # of resources * sizeof(entry)
.
- RAM: Clean Memory, with poor locality (most is paged in after a short amount of time)
- Start-up: No effect beyond page faults.
- Its table-based file format means it's important to keep the number of unique configs as small as possible.
Files that are packaged within the res/
directory of the .apk
. These also
have an entry within resources.arsc
that maps to them from a resource ID.
- RAM: None unless resources are accessed.
- Start-up: None unless resources are accessed.
ICU data, V8 snapshot, etc.
- RAM: Clean Memory
- Start-up: No effect beyond page faults.