Optimizing Chrome's Binary Size
Read first: binary_size_explainer.md
This document primarily focuses on Android and Chrome OS where image size is especially important.
General Advice
- Chrome image size on Android and Chrome OS is tightly limited.
- Non trivial increases to the Chrome image size need to be included in the Feature proposal process.
- Use Compressed resources wherever possible. This is particularly important for images and WebUI resources, which can be substantial.
- Recently a CrOS Image Size Code Mauve (googlers only) was called due to growth concerns.
Size Optimization Help
Feel free to email binary-size@chromium.org.
Compressed resources
Grit supports gzip and brotli compression for resources in the .grd files used to build the resources.pak
file.
Note that compress="gzip"
is already the default behavior for HTML, JS, CSS and SVG files, when the compress
attribute is not specified.
- Choose between gzip (default) or brotli (with
compress="brotli"
) as follows- gzip compression for highly-compressible data typically has minimal impact on load times (but it is worth measuring this, see webui_load_timer.cc for an example of measuring load times).
- Brotli compresses more but is much slower to decompress. Use brotli only when performance doesn't matter (e.g. internals pages).
- Android: Look at the SuperSize reports from the android-binary-size trybot to look for unexpected resources, or unreasonably large symbols.
Chrome binary size
Changes that will significantly increase the Chrome binary size should be made with care and consideration:
- Changes that introduce new libraries can have significant impact and should go through the Feature proposal process.
- Changes intended to replace existing functionality with significant new code should include a deprecation plan for the code being replaced.
Chrome OS Focused Advice
Compressed l10n Strings
Strings do not compress well individually, but an entire l10n file compresses very well.
There are two mechanisms for compressing Chrome l10n files.
- Compressed .pak files
- For desktop Chrome, string resource files generate individual .pak files, e.g.
generated_resources_en.pak
.
These get combined into locale specific .pak files, e.g. locales/en-US.pak
- On Chrome OS, we set
'compress = true
in chrome_repack_locales.gni, which causes these .pak files to be gzip compressed.
(Chrome identifies them as compressed by parsing the file header). - So, Chrome strings on Chrome OS will be compressed by default, nothing else needs to be done!
- Compressing .json l10n files
- Extensions and apps store l10n strings as
messages.json
files in {extension dir}/_locales/{locale}
.- For Chrome OS component extensions (e.g. ChromeVox), we include these extensions as part of the Chrome image.
- These strings get localized across 50+ languages, so it is important to compress them.
- For component extensions only, these files can be gzip compressed (and named
messages.json.gz
) as part of their build step. - For extensions using GN:
- Specify
type="chrome_messages_json_gzip"
for each <output>
entry in the .grd file. - Name the outputs
messages.json.gz
in the .grd and strings.gni files.
- See https://crbug.com/1023568 for details and an example CL.
chromeos-assets
- Input methods, speech synthesis, and apps consume a great deal of disk space on the Chrome OS rootfs partition.
- These assets are not part of the chromium repository, however they do affect rootfs size on devices.
- Proposed additions or increases to chromeos-assets should go through the Feature proposal process and should consider using some form of Downloadable Content if possible.
Android Focused Advice
Optimizing Translations (Strings)
- Use Android System strings where appropriate
- Ensure that strings in .grd files need to be there. For strings that do not need to be translated, put them directly in source code.
Optimizing Images
- Would a vector image work?
- Images that can be described by a series of paths should generally be stored as vectors.
- For images used in native code: VectorIcon.
- For Android drawables: VectorDrawable.
- Would lossy compression make sense (often true for large images)?
- If so, use lossy webp.
- And omit some densities (e.g. add only an xxhdpi version).
- For lossless
.png
images, see how few unique colors you can use without a noticeable difference.- This can often reduce an already optimized .png by 33%-50%.
- Use pngquant to try this out.
- Requires trial and error for each number of unique colors.
- Use one of the GUI tools linked from the website to do this easily.
- Finally - Ensure .png files are fully optimized.
What Build-Time Image Optimizations are There?
- For non-ninepatch images,
drawable-xxxhdpi
are omitted (they are not perceptibly different from xxhdpi in most cases). - For non-ninepatch images within res/ directories (not for .pak file images), they are converted to webp.
- Use the
android-binary-size
trybot to see the size of the images as webp, or just build ChromePublic.apk
and use unzip -l
to see the size of the images within the built apk.
Optimizing Android Resources
- Use config-specific resource directories sparingly.
Optimizing Code
In most parts of the codebase, you should try to optimize your code for binary size rather than performance. Most code runs “fast enough” and only needs to be performance-optimized if identified as a hot spot. Individual code size affects overall binary size no matter the utility of the code.
What this could mean in practice?
- Use a linear search over an array rather than a binary search over a sorted one.
- Reuse common code rather than writing optimized purpose-specific code.
Practical advice:
- When making changes, look at symbol breakdowns with SuperSize reports from the android-binary-size trybot.
- Ensure no symbols exist that are used only by tests.
- Be concise with strings used for error handling.
- Identical strings throughout the codebase are de-duped. Take advantage of this for log strings and exception messages.
- For exceptions, prefer to omit a message altogether unless it provides more detail than the stack trace will.
Optimizing Native Code
- If there's a notable increase in
.data.rel.ro
: - If there's a notable increase in
.rodata
:- See if it would make sense to compress large symbols here by moving them to .pak files.
- Gut-check that all unique string literals being added are actually useful.
- If there's a notable increase in
.text
:- If there are a lot of symbols from C++ templates:
- Try to leverage identical-code-folding as much as possible by making the shape of your code consistent.
- E.g. Use PODs wherever possible, and especially in containers. They will likely compile down to the same code as other pre-existing PODs.
- Try also to use consistent field ordering within PODs.
- E.g. a
std::vector
of bare pointers will very likely be ICF'ed, but one that uses smart pointers gets type-specific destructor logic inlined into it. - This advice is especially applicable to generated code.
- If symbols are larger than expected, use the
Disassemble()
feature of supersize console
to see what is going on.- Watch out for inlined constructors & destructors. E.g. having parameters that are passed by value forces callers to construct objects before calling.
- E.g. For frequently called functions, it can make sense to provide separate
const char *
and const std::string&
overloads rather than a single base::StringPiece
.
Optimizing Java Code
- If you're adding a new feature, see if it makes sense for it to be packaged into its own feature split. E.g.:
- Has a non-trivial amount of Dex (>50kb)
- Not needed on startup
- Has a small integration surface (calls into it must be done with reflection).
- Prefer fewer large JNI calls over many small JNI calls.
- Minimize the use of class initializers (
<clinit>()
).- If R8 cannot determine that they are “trivial”, they will prevent inlining of static members on the class.
- In C++, static objects are created at compile time, but in Java they are created by executing code within
<clinit>()
. There is often little advantage to initializing class fields statically vs. upon first use.
- Try to use default values for fields rather than explicit initialization.
- E.g. Name booleans such that they start as “false”.
- E.g. Use integer sentinels that have initial state as 0.
- Minimize the number of callbacks / lambdas that each API requires.
- Each callback / lambda is syntactic sugar for an anonymous class, and all classes have a constructor in addition to the callback method.
- E.g. rather than have
onFailure()
vs onSuccess()
, have an onFinished(bool)
. - E.g. rather than have
onTextChanged(newValue)
, onDateChanged(newValue)
, ..., have a single onChanged()
, where callbacks use getters to retrieve the new values.- This design allows classes to use a shared callback for multiple listeners.
- This design simplifies data flow by forcing the use of getters (assuming getters exist in the first place).
- Do not override
equals()
, toString()
, hashCode()
unless necessary. Since these methods are defined on Object
, R8 can basically never remove them. - Ensure unused code is optimized away by R8.
- See here for more info on how Chrome uses ProGuard.
- Add
@CheckDiscard
to methods or classes that you expect R8 to inline. - Guard code with
BuildConfig.ENABLE_ASSERTS
to strip it in release builds. - Use //third_party/r8/playground to figure out how various coding patterns are optimized by R8.
- Build with
enable_proguard_obfuscation = false
and use //third_party/android_sdk/public/build-tools/*/dexdump
to see how code was optimized directly in apk / bundle targets.
Optimizing Third-Party Android Dependencies
- Look through SuperSize symbols to see whether unwanted functionality is being pulled in.
- Consider removing all resources via
strip_resources = true
. - Remove specific drawables via
resource_exclusion_regex
.