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.

  1. 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!
  2. 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.
      2. 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