Optimizing Chrome's Binary Size

This advice focuses on Android.

How To Tell if It's Worth Spending Time on Binary Size?

  • Binary size is a shared resource, and thus its growth is largely due to the tragedy of the commons.
  • It typically takes about a week of engineering time to reduce Android's binary size by 50kb.
  • As of 2019, Chrome for Android (arm32) grows by about 100kb per week.

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 Non-Image Native Resources in .pak Files

  • Ensure compress="gzip" or compress="brotli" is used for all highly-compressible (e.g. text) resources.
    • Brotli compresses more but is much slower to decompress. Use brotli only when performance doesn't matter (e.g. internals pages).
  • Look at the SuperSize reports from the android-binary-size trybot to look for unexpected resources, or unreasonably large symbols.

Optimizing Images

  • Would a vector image work?
  • 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).
  • Would near-lossless compression make sense?
    • This can often reduce size by >50% without a perceptible difference.
    • Use pngquant to try this out (use one of the GUI tools to compare before/after).
  • Are the lossless images 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 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 exists 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 error-related strings.
  • 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 moving functions that don't use template parameters to non-templated helper functions).
      • And extract parts of functions that don't use them into helper functions.
    • 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 by 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.

Android-specific advice:

  • 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.
  • Don't use default interface methods on interfaces with multiple implementers.
    • Desugaring causes the methods to be added to every implementor separately.
    • It's more efficient to use a base class to add default methods.
  • Use config-specific resource directories sparingly.
  • Use String.format() instead of concatenation.
    • Concatenation causes a lot of StringBuilder code to be generated.
  • 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(), onDateChanged(), ..., have a single onChanged() that assumes everything changed.

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.
    • Try adding -assumenosideeffects rules to strip out unwanted calls.
  • Consider removing all resources via strip_resources = true.
  • Remove specific drawables via resource_blacklist_regex.

Size Optimization Help