# Optimizing Chrome's Binary Size
Read first: [binary_size_explainer.md](binary_size_explainer.md)
>
> This document primarily focuses on Android and Chrome OS where image size
> is especially important.
>
[TOC]
## 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.
[CrOS Image Size Code Mauve]: http://go/cros-image-size-code-mauve
[Compressed resources]: #Compressed-resources
### Size Optimization Help
Feel free to email [[email protected]](https://groups.google.com/a/chromium.org/forum/#!forum/binary-size).
### 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.
[Grit]: https://www.chromium.org/developers/tools-we-use-in-chromium/grit
[webui_load_timer.cc]: https://cs.corp.google.com/eureka_internal/chromium/src/chrome/browser/ui/webui/webui_load_timer.cc
### 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 binary size]: https://drive.google.com/a/google.com/open?id=1aeIxj8jPOimmlnqD7PvS6np51DZa96dcF72N6CtO6N8
## 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.
1. Compressed .pak files
* For desktop Chrome, string resource files generate individual .pak
files, e.g. `generated_resources_en.pak`.<br/>
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.<br/>
(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!
1. 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:
1. Specify `type="chrome_messages_json_gzip"` for each `<output>`
entry in the .grd file.
1. Name the outputs `messages.json.gz` in the .grd and strings.gni
files.
* See https://crbug.com/1023568 for details and an example CL.
[chrome_repack_locales.gni]: https://cs.chromium.org/chromium/src/chrome/chrome_repack_locales.gni
### 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.
[rootfs size]: https://docs.google.com/document/d/1d3Y2ngMGEP_yfxBFrgOE-dinDILfyAUY9LT0JLlq6zg/edit?usp=sharing
[Downloadable Content]: https://docs.google.com/presentation/d/1wM-eDX-BQavecQz20gPxRF6CxWp1k1sh7zSKMxz4BLI/edit?usp=sharing
## Android Focused Advice
### Optimizing Translations (Strings)
* Use [Android System strings](https://developer.android.com/reference/android/R.string.html) 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](https://chromium.googlesource.com/chromium/src/+/HEAD/components/vector_icons/README.md).
* For Android drawables: [VectorDrawable](https://developer.android.com/guide/topics/graphics/vector-drawable-resources).
* Convert from `.svg` following [this guide](https://developer.android.com/studio/write/vector-asset-studio.html#svg).
* (Googlers): Find most icons as .svg at [go/icons](https://goto.google.com/icons).
* Would **lossy** compression make sense (often true for large images)?
* If so, [use lossy webp](https://codereview.chromium.org/2615243002/).
* 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](https://pngquant.org) 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](https://cs.chromium.org/chromium/src/tools/resources/optimize-png-files.sh).
* There is some [Googler-specific guidance](https://goto.google.com/clank/engineering/best-practices/adding-image-assets) as well.
#### 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.
* Introducing a new config has [a large cost][arsc-bloat].
[arsc-bloat]: https://medium.com/androiddevelopers/smallerapk-part-3-removing-unused-resources-1511f9e3f761#0b72
### 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][size-trybot].
* Or use [//tools/binary_size/diagnose_bloat.py][diagnose_bloat] 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.
#### Optimizing Native Code
* If there's a notable increase in `.data.rel.ro`:
* Ensure there are not [excessive relocations][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][template_bloat_one]).
* Or see if the signature can be re-worked such that there are
[fewer variants of template parameters](template_bloat_two).
* 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`][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][proguard-build-doc] 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][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.
[feature split]: /docs/android_dynamic_feature_modules.md
[proguard-build-doc]: /build/android/docs/java_optimization.md
[size-trybot]: /tools/binary_size/README.md#Binary-Size-Trybot-android_binary_size
[diagnose_bloat]: /tools/binary_size/README.md#diagnose_bloat_py
[relocations]: /docs/native_relocations.md
[template_bloat_one]: https://bugs.chromium.org/p/chromium/issues/detail?id=716393
[template_bloat_two]: https://chromium-review.googlesource.com/c/chromium/src/+/2639396
[supersize-console]: /tools/binary_size/README.md#Usage_console
[r8-playground]: /third_party/r8/playground
### Optimizing Third-Party Android Dependencies
* 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](/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`.
[-whyareyoukeeping]: https://r8-docs.preemptive.com/#keep-rules
[-assumenosideeffects]: https://r8-docs.preemptive.com/#general-rules
[Feature proposal process]: http://www.chromium.org/developers/new-features