Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Supersonic Speed with JVM Args

Supersonic Speed with JVM Args

Demystifying JVM arguments and how they effect Android builds so that you can get the best performance out of your builds. The concepts are greatly impacted by Gradle properties and build caching, therefore this talk discusses them both and how they impact each other.

Jason Pearson

December 07, 2023
Tweet

More Decks by Jason Pearson

Other Decks in Technology

Transcript

  1. CI experience since 2011. Built Android at Hinge, went from

    solo dev to 24 Android engineers. Also spent years in backend and fullstack roles across various companies.
  2. Make it Faster 12 9 6 3 0 Unit Tests

    Release R8 UI Tests Unit Tests Release R8 UI tests minutes
  3. ~400 Modules 💻 290k lines of Kotlin 24 active contributors

    Context Dagger Hilt (KAPT) 🗡 Gradle Kotlin Scripts KSP (except Dagger)
  4. 🏗 Automate Building & Publishing 👩🔬 Automate Tests 🔐 Automate

    Security “But why is it taking so long?” ⏰
  5. 🏗 Automate Building & Publishing 👩🔬 Automate Tests 🔐 Automate

    Security “But why is it taking so long?” ⏰
  6. 🏗 Automate Building & Publishing 👩🔬 Automate Tests 🔐 Automate

    Security ⏰ 📦 Caching JVM Args 🥷 Shadow Jobs Original Problems Scale Problems
  7. This is reasonable 12 9 6 3 0 Unit Tests

    Release R8 UI Tests Unit Tests Release R8 UI tests minutes
  8. 12 9 6 3 0 Unit Tests Release R8 UI

    Tests Unit Tests Release R8 UI tests minutes How did we get to this?
  9. 12 9 6 3 0 Unit Tests Release R8 UI

    Tests Unit Tests Release R8 UI tests minutes How did we get to this?
  10. Control 40 30 20 10 0 Partial Cache Hit Cache

    Hit Core Change Cache Hit Leaf Change minutes cache save gradle execution gradle configuration cache restore git Docker 👩🔬 Unit Tests on CI
  11. 💻 Local Support Gradle Caches Build Cache 💻 💻 💻

    Configuration Cache Dependency Cache Script Cache Local Support Local Support Local Support
  12. 💻 Local Support Gradle Caches Build Cache 💻 💻 💻

    Configuration Cache Dependency Cache Script Cache Local Support Local Support Local Support
  13. Gradle Cache directories ~/.gradle ├── caches │ ├── 8.1 │

    ├── 8.2 │ ├── ⋮ │ ├── buildcache—1 │ ├── jars-9 │ ├── journal-1 │ ├── modules-2 │ └── transforms-3 │ ├── ⋮
  14. Gradle Cache directories ~/.gradle ├── caches │ ├── 8.1 │

    ├── 8.2 │ ├── ⋮ │ ├── buildcache—1 │ ├── jars-9 │ ├── journal-1 │ ├── modules-2 │ └── transforms-3 │ ├── ⋮ Dependency Cache
  15. Gradle Cache directories ~/.gradle ├── caches │ ├── 8.1 │

    ├── 8.2 │ │ ├── generated-gradle-jars │ │ └── kotlin-dsl │ ├── ⋮ │ ├── buildcache—1 │ ├── jars-9 │ ├── journal-1 │ ├── modules-2 │ └── transforms-3 │ Dependency Cache Script Cache
  16. 💻 Local Support Gradle Caches Build Cache 💻 💻 💻

    Configuration Cache Dependency Cache Script Cache Local Support Local Support Local Support
  17. Gradle Caches Build Cache 💻 Configuration Cache Dependency Cache Script

    Cache Local Support 💻 Local Support Remote Support Remote Support
  18. Control Cache build dependencies 40 30 20 10 0 Partial

    Cache Hit Cache Hit Core Change Cache Hit Leaf Change minutes cache save gradle execution gradle configuration cache restore git Docker
  19. Control 40 30 20 10 0 Partial Cache Hit Cache

    Hit Core Change Cache Hit Leaf Change Cache build dependencies minutes cache save gradle execution gradle configuration cache restore git Docker
  20. # If you don't turn these on you aren't #

    getting any performance wins from # Gradle Build Cache or modularization. org.gradle.caching=true org.gradle.parallel=true gradle.properties
  21. # Kotlin 1.7+ only # Good reason to upgrade if

    you are on # an older version. kotlin.incremental.useClasspathSnapshot =true gradle.properties
  22. # Incremental compilation with KAPT. # Only if you still

    use KAPT. kapt.incremental.apt =true gradle.properties
  23. # Stop generating BuildConfig Java stubs. # If this is

    the last Java in your codebase # you could see a decent performance win. android.enableBuildConfigAsBytecode =true gradle.properties
  24. # Stop generating BuildConfig files # if you don't use

    them. android.defaults.buildfeatures.buildconfig =false gradle.properties
  25. # Experimental flag for parallelizing # R8 work - only

    useful if you have # multiple R8 tasks. android.r8.maxWorkers =2 gradle.properties
  26. Gradle Properties Other engineers ask me, "Where do you find

    these?" github.com/androidx/androidx and other OSS projects
  27. Control 40 30 20 10 0 Partial Cache Hit Cache

    Hit Core Change Cache Hit Leaf Change Gradle Properties minutes cache save gradle execution gradle configuration cache restore git Docker
  28. Control 40 30 20 10 0 Partial Cache Hit Cache

    Hit Core Change Cache Hit Leaf Change minutes cache save gradle execution gradle configuration cache restore git Docker Gradle Properties
  29. Gradle Caches Build Cache 💻 Configuration Cache Dependency Cache Script

    Cache Local Support 💻 Local Support Remote Support Remote Support
  30. Gradle Caches Build Cache 💻 Configuration Cache Dependency Cache Script

    Cache Local Support 💻 Local Support Remote Support Remote Support
  31. Gradle Build Cache - Modularize • Reduce the number of

    inputs per task and make them more stable • Watch Siggi’s talk about graph theory • Gradle build scans to observe performance
  32. Gradle Remote Cache • Do it: https://docs.gradle.com/build-cache-node/ • 90 Days,

    300GB, use validation scripts • Most local and CI runs will benefit from build cache hits • Free
  33. • Provides support for features beyond remote cache • Really

    helps figure out failures, cache misses, and performance issues across a team • Looking forward to using test prediction.
  34. Gradle Caches Build Cache 💻 Configuration Cache Dependency Cache Script

    Cache Local Support 💻 Local Support Remote Support Remote Support
  35. Gradle Caches Build Cache 💻 Configuration Cache Dependency Cache Script

    Cache Remote Support Local Support Remote Support Remote Support
  36. Control 40 30 20 10 0 Partial Cache Hit Cache

    Hit Core Change Cache Hit Leaf Change minutes cache save gradle execution gradle configuration cache restore git Docker Gradle Build Cache Optimization
  37. Control 40 30 20 10 0 Partial Cache Hit Cache

    Hit Core Change Cache Hit Leaf Change minutes cache save gradle execution gradle configuration cache restore git Docker Gradle Build Cache Optimization
  38. • Yes, 19+ • 20 and 21 make incremental improvements

    to G1GC algorithm Is it worth it to use the latest JDK
  39. Control JDK 11 -> JDK 19 40 30 20 10

    0 Partial Cache Hit Cache Hit Core Change Cache Hit Leaf Change minutes cache save gradle execution gradle configuration cache restore git Docker
  40. Control 40 30 20 10 0 Partial Cache Hit Cache

    Hit Core Change Cache Hit Leaf Change minutes cache save gradle execution gradle configuration cache restore git Docker JDK 11 -> JDK 19
  41. 🥷 Shadow Jobs Run nightly checks against your project with

    latest snapshots of new versions. “Will version X of dependency Y work on our project?” Zac Sweers
  42. 🥷 Shadow Jobs Run nightly checks against your project with

    latest snapshots of new versions. “Will version X of dependency Y work on our project?” Zac Sweers
  43. JVM Argument Tuning DISCLAIMER: I am not accountable for any

    of these settings. If they don’t work for you there are so many reasons why your setup would handle them differently.
  44. Garbage Collection Algo Tradeoff Each JVM GC algorithm makes a

    choice about what they optimize for and how much.
  45. ParallelGC Garbage Collection Algo Tradeoff G1GC • Optimize for speed

    • Maximum footprint • Might have longer GC pauses • Balance throughput • Lower footprint • Shorter GC pauses
  46. ParallelGC Garbage Collection Algo Tradeoff G1GC • Optimize for speed

    • Maximum footprint • Might have longer GC pauses • Balance throughput • Lower footprint • Shorter GC pauses Probably safe bet to be stable and fastest for most projects Might be faster for some projects
  47. ParallelGC Garbage Collection Algo Tradeoff G1GC • Optimize for speed

    • Maximum footprint • Might have longer GC pauses • Balance throughput • Lower footprint • Shorter GC pauses • JDK 20 + 21 improvements! Probably safe bet to be stable and fastest for most projects Might be faster for some projects
  48. kotlin.daemon.jvm.options When passing arguments, follow these rules: Use the minus

    sign - only before the arguments Xmx, XX:MaxMetaspaceSize, and XX:ReservedCodeCacheSize. Separate arguments with commas (,) without spaces. Arguments that come after a space will be used for the Gradle daemon, not for the Kotlin daemon. Kotlin JVM Arguments
  49. kotlin.daemon.jvm.options When passing arguments, follow these rules: Use the minus

    sign - only before the arguments Xmx, XX:MaxMetaspaceSize, and XX:ReservedCodeCacheSize. Separate arguments with commas (,) without spaces. Arguments that come after a space will be used for the Gradle daemon, not for the Kotlin daemon. Kotlin JVM Arguments
  50. kotlin.daemon.jvm.options When passing arguments, follow these rules: Use the minus

    sign - only before the arguments Xmx, XX:MaxMetaspaceSize, and XX:ReservedCodeCacheSize. Separate arguments with commas (,) without spaces. Arguments that come after a space will be used for the Gradle daemon, not for the Kotlin daemon. Kotlin JVM Arguments
  51. kotlin.daemon.jvmargs You can add the kotlin.daemon.jvmargs property in the gradle.properties

    fi le: kotlin.daemon.jvmargs=-Xmx1500m -Xms=500m Kotlin JVM Arguments
  52. kotlin.daemon.jvmargs You can add the kotlin.daemon.jvmargs property in the gradle.properties

    fi le: kotlin.daemon.jvmargs=-Xmx1500m -Xms=500m Using spaces at the beginning will cause all arguments to be ignored Kotlin JVM Arguments
  53. Xmx (max) JVM Heap Size Xms (min) • Amount of

    memory it takes to compile your biggest task / module. • For most projects this is going to be your main app module’s minification step. • Somewhere between 80 and 100% of Xmx. • Heap only grows when GC happens, so increasing it avoids this penalty.
  54. MaxMetaspaceSize Metaspace CompressedClassSpaceSize • class & non-class space • unlimited

    by default • virtual size of compressed classes • defaults to 1GB
  55. CompressedOops Metaspace CompressedClassPointers • Defaults on < 30GB heap •

    Uses 4 fewer bytes • Restricts memory heap to 32GB • Available if CompressedOops is on
  56. Metaspace CompressedOops & unlimited MaxMetaspace is going to be better

    for majority of Android projects. (so stick with the defaults)
  57. SoftRefLRUPolicyMSPerMB The number of milliseconds to hold onto soft references

    per MB of heap size, default 1000 = 2h 16m 32s 8GB Heap @ 1000 MS Policy
  58. SoftRefLRUPolicyMSPerMB The number of milliseconds to hold onto soft references

    per MB of heap size, default 1000 = 2h 16m 32s 8GB Heap @ 1000 MS Policy 8GB Heap @ 10 MS Policy = 0h 1m 22s
  59. SoftRefLRUPolicyMSPerMB The number of milliseconds to hold onto soft references

    per MB of heap size, default 1000 = 2h 16m 32s 8GB Heap @ 1 MS Policy 8GB Heap @ 1000 MS Policy = 0h 0m 8s 8GB Heap @ 10 MS Policy = 0h 1m 22s
  60. Gradle Max Workers --max-workers The maximum number of active workers

    to allow Should probably be number of performance cores, possibly 1-2 less Will not spawn more workers is memory heap requirements cannot be met
  61. malloc alternatives Therefore use jemalloc (or tcmalloc) to be able

    to run more daemons in parallel with less memory pressure.
  62. 🥷 Shadow Jobs 1. Instrument your build with JVM args

    as parameters 2. Run parameterized tests 3. Profit What do we actually pick for JVM args?
  63. • GC Algo = It Depends • SoftRefLRUPolicyMSPerMB = 1

    • CodeCache = 256MB - 512MB • MaxMetaspace = Some GB or unlimited • Metaspace = 64MB - 1GB • Xmx = 4GB - 12GB • Xms = Xmx • Max Workers=Performance Cores - 2 What do we actually pick?
  64. • jcmp for Metaspace • –XX:+PrintCodeCache for CodeCache • Measure

    CPU & Memory resource usage, lots of tools available JVM Measurement Tools
  65. Control JVM Optimizations 40 30 20 10 0 Partial Cache

    Hit Cache Hit Core Change Cache Hit Leaf Change minutes cache save gradle execution gradle configuration cache restore git Docker
  66. Control 40 30 20 10 0 Partial Cache Hit Cache

    Hit Core Change Cache Hit Leaf Change minutes cache save gradle execution gradle configuration cache restore git Docker JVM Optimizations
  67. Control 40 30 20 10 0 Partial Cache Hit Cache

    Hit Core Change Cache Hit Leaf Change minutes cache save gradle execution gradle configuration cache restore git Docker JVM Optimizations
  68. Gradle Caches Build Cache Configuration Cache Dependency Cache Script Cache

    Remote Support Remote Support Remote Support 💻 Local Support
  69. Gradle Caches Build Cache Configuration Cache Dependency Cache Script Cache

    Remote Support Remote Support Remote Support 💻 Local Support
  70. Gradle Cache directories ~/.gradle ├── caches │ └── ⋮ ~/project

    ├── .gradle ├── app ├── build ├── gradle └── ⋮ Dependency Cache Script Cache
  71. Gradle Cache directories ~/project ├── .gradle │ │ │ │

    ├── app ├── build ├── gradle └── ⋮
  72. Gradle Cache directories ~/project ├── .gradle │ ├── 8.1 │

    ├── 8.2 │ ├── ⋮ │ └── configuration-cache ├── app ├── build ├── gradle └── ⋮
  73. ~/project ├── .gradle │ ├── 8.1 │ ├── 8.2 │

    ├── ⋮ │ └── configuration-cache ├── app ├── build ├── gradle └── ⋮ Configuration Cache Gradle Cache directories
  74. As of Gradle 8.1 encryption was added. Asking Gradle support

    - configuration cache on CI is unsupported Gradle Configuration Cache on CI
  75. As of Gradle 8.1 encryption was added. Gradle's response has

    been that configuration cache on CI is not supported Gradle Configuration Cache on CI
  76. As of Gradle 8.1 encryption was added. Gradle's response has

    been that configuration cache on CI is not supported. But we persisted anyway... Gradle Configuration Cache on CI
  77. Gradle Configuration Cache on CI Environment Variable Environment Variables Prefixed

    File system Entry File System property Value from custom source 35 1 2007 4 52 5 build/reports/configuration-cache/4tnz..i67j/27o6..b0jl/configuration-cache-report.html We looked at the reports for all inputs
  78. But despite everything 100% of inputs being consistent we continued

    to get an unhelpful error message: Gradle Configuration Cache on CI
  79. Gradle Configuration Cache on CI Calculating task graph as no

    configuration cache is available for tasks
  80. Gradle Configuration Cache on CI “You need to cache the

    encryption key.” — Inaki Villar
  81. Gradle Configuration Cache on CI Caveat to this method -

    you will be caching any secrets used by this build, so either: • Only use this for builds that do not rely on secrets • Knowingly store this with the security risk
  82. Gradle User Home directory ~/.gradle ├── caches │ ├── 8.1

    │ │ … │ ├── 8.4 │ │ └── cc-keystore │ │ ├── cache.properties │ │ ├── cc-keystore.lock │ │ └── gradle.keystore │ ├── │ ├── buildcache—1 │ ├── jars-9 │ ├── journal-1 Configuration Cache Encryption
  83. Gradle Configuration Cache on CI Calculating task graph as no

    configuration cache is available for tasks
  84. unit_tests: docker: - image: circleci/android:api-34-node steps: - run: name: Clean

    Gradle Cache command: | find . -type f -name '*.lock' -delete find . -type f -name 'gc.properties' -delete find ~/.gradle -type f -name '*.lock' -delete find ~/.gradle -type f -name 'gc.properties' -delete when: always - save_cache: paths: - ~/.gradle/caches/<< parameters.gradle_version >>/ generated-gradle-jars - ~/.gradle/caches/<< parameters.gradle_version >>/ kotlin-dsl - .gradle/configuration-cache - ~/.gradle/caches/<< parameters.gradle_version >>/ cc-keystore - .gradle/<< parameters.gradle_version >> config.yml
  85. unit_tests: docker: - image: circleci/android:api-34-node steps: - run: name: Clean

    Gradle Cache command: | find . -type f -name '*.lock' -delete find . -type f -name 'gc.properties' -delete find ~/.gradle -type f -name '*.lock' -delete find ~/.gradle -type f -name 'gc.properties' -delete when: always - save_cache: paths: - ~/.gradle/caches/<< parameters.gradle_version >>/ generated-gradle-jars - ~/.gradle/caches/<< parameters.gradle_version >>/ kotlin-dsl - .gradle/configuration-cache - ~/.gradle/caches/<< parameters.gradle_version >>/ cc-keystore - .gradle/<< parameters.gradle_version >> config.yml 🤦
  86. Gradle Configuration Cache on CI Calculating task graph as configuration

    cache cannot be reused because an input to task ‘:gradle-plugin:generateExternalPluginSpecBuilders' has changed.
  87. Gradle Configuration Cache on CI Calculating task graph as configuration

    cache cannot be reused because an input to task ‘:gradle-plugin:generateExternalPluginSpecBuilders' has changed.
  88. “Gradle has compilation avoidance for non- ABI changes in included

    builds, but due to a bug any inline function would cause all build scripts to be recompiled even when there is a non-ABI change.” - Nicklas Ansman Gradle Configuration Cache on CI
  89. Gradle Configuration Cache on CI Could not load the value

    of field `patterns` of `org.gradle.configurationcache.serialization.codecs.DirectoryTree Spec` bean found in field `fileSystemInputs` of `org.gradle.configurationcache.fingerprint.ConfigurationCacheFing erprint$WorkInputs` bean found in Gradle runtime. > Failed to instrument class org/jetbrains/kotlin/gradle/tasks/ KotlinCompile$ScriptFilterSpec in ClassLoaderScopeIdentifier.Id{coreAndPlugins:settings[:gradle- plugin]:buildSrc[:gradle-plugin]:root-project[:gradle-plugin] (export)}
  90. Gradle Configuration Cache on CI Could not load the value

    of field `patterns` of `org.gradle.configurationcache.serialization.codecs.DirectoryTree Spec` bean found in field `fileSystemInputs` of `org.gradle.configurationcache.fingerprint.ConfigurationCacheFing erprint$WorkInputs` bean found in Gradle runtime. > Failed to instrument class org/jetbrains/kotlin/gradle/tasks/ KotlinCompile$ScriptFilterSpec in ClassLoaderScopeIdentifier.Id{coreAndPlugins:settings[:gradle- plugin]:buildSrc[:gradle-plugin]:root-project[:gradle-plugin] (export)}
  91. ~/.gradle/caches/<< gradle_version >>/generated-gradle-jars ~/.gradle/caches/<< gradle_version >>/kotlin-dsl ~/.gradle/caches/<< gradle_version >>/cc-keystore ~/.gradle/caches/modules-2

    ~/.gradle/caches/jars-9 ~/.gradle/caches/transforms-3 .gradle/<< gradle_version >> .gradle/configuration-cache gradle-plugin/build ci-cache.yml
  92. “Wow, I was prepared for a faster PR time but

    that was blazing” - Jacob Duron Gradle Configuration Cache on CI
  93. “I believe its in the interest of Gradle and the

    community to support a method to share the configuration cache between machines that doesn't expose build secrets” - me Gradle Configuration Cache Update
  94. TLDR; Wait for Gradle 8.6 and a new GitHub Gradle

    Build Action Gradle Configuration Cache Update
  95. Gradle Caches Build Cache Configuration Cache Dependency Cache Script Cache

    Remote Support Remote Support Remote Support 💻 Local Support
  96. Gradle Caches Build Cache Configuration Cache Dependency Cache Script Cache

    Remote Support Remote Support Remote Support Remote Support
  97. Control Gradle Configuration Cache Reuse 40 30 20 10 0

    Partial Cache Hit Cache Hit Core Change Cache Hit Leaf Change minutes cache save gradle execution gradle configuration cache restore git Docker
  98. Control Gradle Configuration Cache Reuse 40 30 20 10 0

    Partial Cache Hit Cache Hit Core Change Cache Hit Leaf Change minutes cache save gradle execution gradle configuration cache restore git Docker
  99. 40 30 20 10 0 Gradle Configuration Cache Reuse Control

    Partial Cache Hit Cache Hit Core Change Cache Hit Leaf Change minutes cache save gradle execution gradle configuration cache restore git Docker
  100. Overhead Cost Optimizations 📦 git clone pulls 400 MB big

    images (2.5 GB) Minutes to save cache ~60 seconds to restore cache
  101. GitHub Actions 📦 pulls run at 200mb/s base images are

    cached better cache restore and save operations runs 2-5x faster
  102. Control Present Day Unit Test Runs 40 30 20 10

    0 Partial Cache Hit Cache Hit Core Change Cache Hit Leaf Change minutes cache save gradle execution gradle configuration cache restore git Docker
  103. 40 30 20 10 0 Control Partial Cache Hit Cache

    Hit Core Change Cache Hit Leaf Change minutes cache save gradle execution gradle configuration cache restore git Docker Present Day Unit Test Runs
  104. Gradle Documentation Gradle Files & Directories https://docs.gradle.org/current/userguide/directory_layout.html Gradle CI Ephemeral

    Best Practices https://docs.gradle.org/current/userguide/ dependency_resolution.html#sub:ephemeral-ci-cache Gradle Recommends using Daemons for CI https://docs.gradle.org/current/userguide/ gradle_daemon.html#continuous_integration
  105. Kotlin JVM Args Documentation New Method https://kotlinlang.org/docs/gradle-compilation-and-caches.html#kotlin- daemon-jvmargs-property Deprecated Method

    https://kotlinlang.org/docs/gradle-compilation-and-caches.html#kotlin- daemon-jvm-options-system-property
  106. JDK Version Documentation JDK Release Notes Summaries for GC Algo

    Changes 19 | 20 | 21 JVM SoftReferences Tuning CodeCache