Read first: binary_size_explainer.md
This document primarily focuses on Android and Chrome OS where image size is especially important.
[TOC]
- 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.
Feel free to email [email protected].
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.
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.
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!
- For desktop Chrome, string resource files generate individual .pak
files, e.g.
- 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.
- Specify
- See https://crbug.com/1023568 for details and an example CL.
- Extensions and apps store l10n strings as
- 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.
- 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.
- 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.
- Convert from
.svg
following this guide. - (Googlers): Find most icons as .svg at go/icons.
- Convert from
- 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.
- Use tools/resources/optimize-png-files.sh.
- There is some Googler-specific guidance as well.
- 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 buildChromePublic.apk
and useunzip -l
to see the size of the images within the built apk.
- Use the
- Use config-specific resource directories sparingly.
- Introducing a new config has a large cost.
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.
- Or use //tools/binary_size/diagnose_bloat.py to create diffs locally.
- 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.
- If there's a notable increase in
.data.rel.ro
:- Ensure there are not excessive relocations.
- 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 moving parts of the templatized function that don't use the template parameters to non-templated helper functions).
- Or see if the signature can be re-worked such that there are fewer variants of template parameters.
- 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.
- E.g. Use PODs wherever possible, and especially in containers. They will
likely compile down to the same code as other pre-existing PODs.
- If symbols are larger than expected, use the
Disassemble()
feature ofsupersize 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 *
andconst std::string&
overloads rather than a singlebase::StringPiece
.
- E.g. For frequently called functions, it can make sense to provide
separate
- Watch out for inlined constructors & destructors. E.g. having parameters
that are passed by value forces callers to construct objects before
calling.
- If there are a lot of symbols from C++ templates:
- 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()
vsonSuccess()
, have anonFinished(bool)
. - E.g. rather than have
onTextChanged(newValue)
,onDateChanged(newValue)
, ..., have a singleonChanged()
, 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 onObject
, 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.
- Look through SuperSize symbols to see whether unwanted functionality
is being pulled in.
- Use ProGuard's -whyareyoukeeping to see why unwanted symbols are kept (e.g. to //base/android/proguard/chromium_apk.flags).
- Try adding -assumenosideeffects rules to strip out unwanted calls.
- Consider removing all resources via
strip_resources = true
. - Remove specific drawables via
resource_exclusion_regex
.