Improving Android Build Performance

Improving Android Build Performance

We all know a fast build makes for a happy developer. Unfortunately, as your project grows larger and more complex, its build will become slower if left unchecked. You’ve updated to the latest Android Plugin, but you’re still waiting more than you like, what can you do? Recent Gradle releases give you new capabilities to greatly improve your build speed. This session discusses how you can :

- Identify performance bottlenecks with build scans
- Fix common problems, like slow configuration time, tasks running when they shouldn’t, and builds that don’t parallelize well
- Benefit from incremental annotation processing

F929f5d80ef8a23b67ad8ac6f08416cd?s=128

Cédric Champeau

November 15, 2018
Tweet

Transcript

  1. 3.

    | 3 What is Gradle? - Gradle != Gradle plugin

    for Android != Android Studio - Gradle is a build and automation tool - JVM based - 100% Free Open Source - Apache Standard License 2.0 - Multi-ecosystem - JVM: Java, Kotlin, Groovy, Scala, … - Native: C/C++, Go… - Android - …
  2. 4.

    | 4 Gradle, Inc. The company behind Gradle. • Build

    Happiness • Employs full time engineers • Providing Gradle Build Scans and Gradle Enterprise • (Gradle consulting, support, development services etc.) • (Training: online, public and in-house)
  3. 5.

    | 5 We’re hiring • Fully distributed development team •

    Exciting project used by millions • Build tool team and Gradle enterprise positions If anything you hear from now on sounds like a great problem to solve, Talk to us! https:/ /gradle.com/careers/
  4. 7.

    | 7 60s waste * 50 builds per day *

    50 developers = 42h wasted per day … and that’s not even considering lost focus Fast Builds Matter
  5. 9.

    | 9 Process 1. Define scenario to improve 2. Profile

    scenario 3. Identify biggest bottleneck 4. Fix bottleneck 5. Verify fix by measurement 6. Repeat
  6. 10.

    |10 Automate your measurements github.com/gradle/gradle-profiler configurationTime { tasks = ["help"]

    } sync { tasks = ["generateDebugSources"] } cleanBuild { tasks = ["build"] cleanup-tasks = ["clean"] } cachedCleanBuild { tasks = ["build"] cleanup-tasks = ["clean"] gradle-args = ["--build-cache"] } configurationTime { tasks = ["help"] } sync { tasks = ["generateDebugSources"] } cleanBuild { tasks = ["build"] cleanup-tasks = ["clean"] } cachedCleanBuild { tasks = ["build"] cleanup-tasks = ["clean"] gradle-args = ["--build-cache"] }
  7. 11.

    |11 Stay Up-To-Date ./gradlew wrapper --gradle-version 4.10.2 buildscript { repositories

    { google() } dependencies { classpath ‘com.android.tools.build:gradle:3.1.2' } } buildscript { repositories { google() } dependencies { classpath ‘com.android.tools.build:gradle:3.1.2' } }
  8. 12.

    |12 JVM tuning Provide enough heap space Other tweaks often

    do more harm than good Spend your time on structural improvements
  9. 13.

    |13 The Gradle Daemon The Gradle Daemon is enabled by

    default Provides a number of in-memory caches Useful for repeated builds Do not disable it!
  10. 17.

    |17 Red Flags Startup/Settings/buildSrc > 1s Configuration time >10ms/project Single-line

    change ≅ clean build No-op build doing any work at all High GC time
  11. 24.

    |24 Eager resolution task uberJar(type:Jar) { from sourceSets.main.output from configurations.runtime.collect

    { it.directory ? it : zipTree(it) } classifier "uber-jar" } task uberJar(type:Jar) { from sourceSets.main.output from configurations.runtime.collect { it.directory ? it : zipTree(it) } classifier "uber-jar" } Resolution at Configuration Time
  12. 25.

    |25 Use lazy evaluation instead task uberJar(type:Jar) { inputs.files configurations.runtime

    from sourceSets.main.output from { configurations.runtime.collect { it.directory ? it : zipTree(it) } } classifier "uber-jar" } task uberJar(type:Jar) { inputs.files configurations.runtime from sourceSets.main.output from { configurations.runtime.collect { it.directory ? it : zipTree(it) } } classifier "uber-jar" } Resolution at Configuration Time
  13. 27.

    |27 task projectStats() { def statsFile = new File(buildDir, 'stats.txt')

    statsFile.parentFile.mkdirs() statsFile.text = "Source files: ${sourceSets.main.java.size()}" } task projectStats() { def statsFile = new File(buildDir, 'stats.txt') statsFile.parentFile.mkdirs() statsFile.text = "Source files: ${sourceSets.main.java.size()}" } I/O at Configuration Time Careful when writing custom tasks!
  14. 28.

    |28 I/O at Configuration Time task projectStats() { def statsFile

    = new File(buildDir, 'stats.txt') inputs.files sourceSets.main.java outputs.file statsFile doLast { statsFile.parentFile.mkdirs() statsFile.text = "Source files: ${sourceSets.main.java.size()}" } } task projectStats() { def statsFile = new File(buildDir, 'stats.txt') inputs.files sourceSets.main.java outputs.file statsFile doLast { statsFile.parentFile.mkdirs() statsFile.text = "Source files: ${sourceSets.main.java.size()}" } } Don’t forget doLast {}
  15. 29.

    |29 task projectStats(type: ProjectStats) { statsFile = new File(buildDir, 'stats.txt')

    sources = sourceSets.main.java } class ProjectStats extends DefaultTask { @InputFiles FileCollection sources @OutputFile File statsFile @TaskAction def stats() { statsFile.text = "Source files: ${sources.size()}" } } task projectStats(type: ProjectStats) { statsFile = new File(buildDir, 'stats.txt') sources = sourceSets.main.java } class ProjectStats extends DefaultTask { @InputFiles FileCollection sources @OutputFile File statsFile @TaskAction def stats() { statsFile.text = "Source files: ${sources.size()}" } } I/O at Configuration Time
  16. 31.

    |31 Expensive logic for each project def out = new

    ByteArrayOutputStream() exec { commandLine 'git', 'rev-parse', "HEAD" standardOutput = out workingDir = rootDir } version = new String(out.toByteArray()) def out = new ByteArrayOutputStream() exec { commandLine 'git', 'rev-parse', "HEAD" standardOutput = out workingDir = rootDir } version = new String(out.toByteArray()) subprojects { apply from: "$rootDir/gradle/version-from-status.gradle" } subprojects { apply from: "$rootDir/gradle/version-from-status.gradle" } Inefficient Plugins
  17. 32.

    |32 Reuse expensive calculations def out = new ByteArrayOutputStream() exec

    { commandLine 'git', 'rev-parse', "HEAD" standardOutput = out workingDir = rootDir } version = new String(out.toByteArray()) subprojects { version = rootProject.version } def out = new ByteArrayOutputStream() exec { commandLine 'git', 'rev-parse', "HEAD" standardOutput = out workingDir = rootDir } version = new String(out.toByteArray()) subprojects { version = rootProject.version } Inefficient Plugins
  18. 35.

    |35 Extract Script Plugins Makes finding issues easier apply from:

    "$rootDir/gradle/environment.gradle" apply from: "gradle/chuck-norris-fact.gradle" apply from: "$rootDir/gradle/version-from-status.gradle" subprojects { apply plugin: 'java' apply from: "$rootDir/gradle/test-fixtures.gradle" apply from: "$rootDir/gradle/compiler-settings.gradle" } apply from: "$rootDir/gradle/environment.gradle" apply from: "gradle/chuck-norris-fact.gradle" apply from: "$rootDir/gradle/version-from-status.gradle" subprojects { apply plugin: 'java' apply from: "$rootDir/gradle/test-fixtures.gradle" apply from: "$rootDir/gradle/compiler-settings.gradle" }
  19. 42.

    |42 Example: Crashlytics Unique IDs are the bane of local

    dev performance apply plugin: 'io.fabric' android { buildT ypes { debug { ext.alwaysUpdateBuildId = false } } } apply plugin: 'io.fabric' android { buildT ypes { debug { ext.alwaysUpdateBuildId = false } } }
  20. 43.

    |43 Faster Compilation Modularization => Compile avoidance Decoupled code =>

    Faster incremental compilation Careful with Kotlin migration (for now)
  21. 44.

    |44 Incremental Annotation Processing Since Gradle 4.7 Early adopters: Lombok,

    Android-State, Dagger, Parceller Others: github.com/gradle/gradle/issues/5277
  22. 53.
  23. 54.

    |55 Gradle performance guide https://guides.gradle.org/performance Android performance guide https://developer.android.com/studio/build/optimize-your-build.html Plugin

    development guide https://guides.gradle.org/implementing-gradle-plugins Structuring build logic guide https://docs.gradle.org/current/userguide/organizing_build_logic.html Guides