Cómo generar un perfil para tu compilación

Los proyectos más grandes, o aquellos que implementan una gran cantidad de lógica de compilación predeterminada, pueden exigir que indagues más en el proceso de compilación a fin de detectar cuellos de botella. Puedes hacerlo generando perfiles sobre el tiempo que Gradle tarda en ejecutar cada etapa del ciclo de vida de la compilación y cada tarea de compilación. Por ejemplo, si tu perfil de compilación muestra que Gradle tarda mucho en configurar tu proyecto, eso puede sugerir que necesitas quitar lógica de compilación personalizada de la etapa de configuración. Además, si la tarea mergeDevDebugResources consume una gran cantidad de tiempo de compilación, eso puede indicar que necesitas convertir tus imágenes a WebP o inhabilitar la compresión de PNG.

Si usas Android Studio 4.0 o una versión posterior, la mejor manera de investigar los problemas de rendimiento de compilación es usar Build Analyzer.

Además, existen dos opciones para generar perfiles de tu compilación fuera de Android Studio:

  1. La herramienta de gradle-profiler independiente, una herramienta sólida para realizar un análisis profundo de la compilación.

  2. La opción de --profile de Gradle, una herramienta conveniente disponible desde la línea de comandos de Gradle.

Cómo usar la herramienta de gradle-profiler independiente

A fin de encontrar la configuración del proyecto que te proporcione la mejor velocidad de compilación, deberás usar el generador de perfiles de Gradle, una herramienta para recopilar información de generación de perfiles y comparativas para compilaciones de Gradle. El generador de perfiles de Gradle te permite crear situaciones de compilación y ejecutarlas varias veces, lo que evita una alta variación entre los resultados y garantiza la reproducibilidad de estos.

El modo de comparativas se debe usar para recopilar información sobre compilaciones incrementales y limpias, mientras que el modo de generación de perfiles se puede usar para recopilar información más detallada sobre las ejecuciones, incluidas las instantáneas de CPU.

Entre las opciones de configuración del proyecto para comparativas, se incluyen las siguientes:

  • Versiones del complemento
  • Versiones de Gradle
  • Configuración de JVM (tamaño de montón, tamaño del PermGen, recolección de elementos no utilizados, etc.)
  • Cantidad de trabajadores de Gradle (org.gradle.workers.max)
  • Opciones por complemento para optimizar aún más el rendimiento

Cómo comenzar

  • Instala el generador de perfiles de Gradle siguiendo estas instrucciones.
  • Ejecuta: gradle-profiler --benchmark --project-dir <root-project> :app:assembleDebug

Esto comparará una compilación completamente actualizada, ya que --benchmark ejecuta la tarea varias veces sin realizar cambios en el proyecto. Luego, se generará un informe HTML en el directorio profile-out/ que mostrará los tiempos de compilación.

Existen otras situaciones que pueden ser más útiles para establecer comparativas.

  • Los cambios de código en el cuerpo de un método en una clase en la que haces la mayor parte del trabajo.
  • Cambios en la API en un módulo que se usa en todo tu proyecto. Si bien es menos frecuente que se realicen cambios en tu código, esto tiene un impacto mayor y es útil medirlo.
  • Ediciones de diseño para simular la iteración en el trabajo de la IU.
  • Ediciones de string para simular que se trata de trabajos de traducción.
  • Compilaciones limpias para simular cambios en la compilación en sí misma (p. ej., actualización del complemento de Gradle para Android, actualización de Gradle o ediciones de tu propio código de compilación en buildSrc.

A fin de comparar esos casos de uso, puedes crear una situación que se utilice para impulsar la ejecución de gradle-profiler y que aplique los cambios adecuados a tus fuentes. A continuación, puedes consultar algunas de las situaciones comunes.

Cómo generar perfiles en diferentes configuraciones de memoria/CPU

A fin de comparar diferentes configuraciones de memoria y CPU, puedes crear varias situaciones que usen valores diferentes para org.gradle.jvmargs. Por ejemplo, puedes crear situaciones:

# <root-project>/scenarios.txt
clean_build_2gb_4workers {
    tasks = [":app:assembleDebug"]
    gradle-args = ["--max-workers=4"]
    jvm-args = ["-Xmx2048m"]
    cleanup-tasks = ["clean"]
}
clean_build_parallelGC {
    tasks = [":app:assembleDebug"]
    jvm-args = ["-XX:+UseParallelGC"]
    cleanup-tasks = ["clean"]
}

clean_build_G1GC_4gb {
    tasks = [":app:assembleDebug"]
    jvm-args = ["-Xmx4096m", "-XX:+UseG1GC"]
    cleanup-tasks = ["clean"]
}

Ejecutar gradle-profiler --benchmark --project-dir <root-project> --scenario-file scenarios.txt ejecutará tres situaciones, y podrás comparar cuánto tarda :app:assembleDebug en cada una de estas configuraciones.

Cómo generar perfiles en diferentes versiones del complemento de Gradle

Si deseas averiguar la forma en que el cambio de la versión del complemento de Gradle afecta los tiempos de compilación, crea una situación para obtener comparativas. Eso requiere cierta preparación para que la versión del complemento se pueda insertar desde la situación. Cambia la raíz build.gradle:

# <root-project>/build.gradle
buildscript {
    def agpVersion = providers.systemProperty("agpVersion").forUseAtConfigurationTime().orNull ?: '4.1.0'

    ext.kotlin = providers.systemProperty('kotlinVersion').forUseAtConfigurationTime().orNull ?: '1.4.0'

    dependencies {
        classpath "com.android.tools.build:gradle:$agpVersion"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin"
    }
}

Ahora puedes especificar el complemento de Gradle para Android y las versiones del complemento de Gradle para Kotlin desde el archivo de situaciones, y hacer que la situación agregue un método nuevo a los archivos de origen:

# <root-project>/scenarios.txt
non_abi_change_agp4.1.0_kotlin1.4.10 {
    tasks = [":app:assembleDebug"]
    apply-abi-change-to ["app/src/main/java/com/example/your_app/your_code_file.java,
                              "app/src/main/java/com/example/your_app/your_code_file.kt"]
    System-properties {
      "agpVersion" = "4.1.0"
      "kotlinVersion" = "1.4.10"
}

non_abi_change_agp4.2.0_kotlin1.4.20 {
    tasks = [":app:assembleDebug"]
    apply-abi-change-to ["app/src/main/java/com/example/your_app/your_code_file.java,
                              "app/src/main/java/com/example/your_app/your_code_file.kt"]
    System-properties {
      "agpVersion" = "4.2.0-alpha16"
      "kotlinVersion" = "1.4.20"
}

Cómo generar perfiles en una compilación incremental

La mayoría de las compilaciones son incrementales, lo que hace que esta sea una de las situaciones más importantes para generar perfiles. El generador de perfiles de Gradle es muy amplio para generar perfiles en compilaciones incrementales. Puede aplicar cambios en un archivo de origen de manera automática cambiando el cuerpo del método, agregando un método nuevo o cambiando un recurso de diseño o de string. Por ejemplo, puedes crear situaciones incrementales como la siguiente:

# <root-project>/scenarios.txt
non_abi_change {
    tasks = [":app:assembleDebug"]
    apply-non-abi-change-to = ["app/src/main/java/com/example/your_app/your_code_file.java,
                              "app/src/main/java/com/example/your_app/your_code_file.kt"]
}

abi_change {
    tasks = [":app:assembleDebug"]
    apply-abi-change-to = ["app/src/main/java/com/example/your_app/your_code_file.java,
                              "app/src/main/java/com/example/your_app/your_code_file.kt"]
}

layout_change {
    tasks = [":app:assembleDebug"]
    apply-android-layout-change-to = "app/src/main/res/your_layout_file.xml"
}
string_resource_change {
    tasks = [":app:assembleDebug"]
    apply-android-resource-value-change-to = "app/src/main/res/values/strings.xml"
}

Si ejecutas gradle-profiler --benchmark --project-dir &lt;root-project> --scenario-file scenarios.txt, se generará el informe HTML con los datos de comparativas.

Puedes combinar situaciones incrementales con otras configuraciones, como el tamaño de montón, la cantidad de trabajadores o la versión de Gradle:

# <root-project>/scenarios.txt
non_abi_change_4g {
    tasks = [":app:assembleDebug"]
    apply-non-abi-change-to ["app/src/main/java/com/example/your_app/your_code_file.java,
                              "app/src/main/java/com/example/your_app/your_code_file.kt"]
    jvm-args = ["-Xmx4096m"]
}

non_abi_change_4g_8workers {
    tasks = [":app:assembleDebug"]
    apply-non-abi-change-to ["app/src/main/java/com/example/your_app/your_code_file.java,
                              "app/src/main/java/com/example/your_app/your_code_file.kt"]
    jvm-args = ["-Xmx4096m"]
    gradle-args = ["--max-workers=8"]
}

non_abi_change_3g_gradle67 {
    tasks = [":app:assembleDebug"]
    apply-non-abi-change-to ["app/src/main/java/com/example/your_app/your_code_file.java,
                              "app/src/main/java/com/example/your_app/your_code_file.kt"]
    jvm-args = ["-Xmx3072m"]
    version = ["6.7"]
}

Cómo generar perfiles en una compilación limpia

A fin de comparar una compilación limpia, puedes crear una situación que se usará para impulsar la ejecución del generador de perfiles de Gradle:

# <root-project>/scenarios.txt
clean_build {
    tasks = [":app:assembleDebug"]
    cleanup-tasks = ["clean"]
}

Para ejecutar esta situación, usa el siguiente comando:

gradle-profiler --benchmark --project-dir <root-project> --scenario-file scenarios.txt

Cómo usar la opción de --profile de Gradle

Para generar y ver un perfil de compilación desde la línea de comandos de Gradle, sigue los siguientes pasos:

  1. Abre una terminal de línea de comandos en la raíz de tu proyecto.
  2. Realiza una compilación limpia ingresando el siguiente comando. A medida que generes el perfil de tu compilación, deberás realizar una compilación limpia entre cada compilación de la que generes perfiles, ya que Gradle omite tareas cuando las entradas de estas (como el código fuente) no cambian. De este modo, una segunda compilación sin cambios en las entradas siempre se ejecuta más rápido porque las tareas no se vuelven a ejecutar. Por lo tanto, ejecutar la tarea clean entre tus compilaciones garantiza la generación de un perfil del proceso de compilación completo.
    // On Mac or Linux, run the Gradle wrapper using "./gradlew".
    gradlew clean
    
  3. Ejecuta una compilación de depuración de una de tus variantes de productos, como la variante "dev", con las siguientes marcas:
    gradlew --profile --offline --rerun-tasks assembleFlavorDebug
    
    • --profile: Habilita la generación de perfiles.
    • --offline: Evita que Gradle obtenga dependencias en línea. Esto garantiza que las demoras provocadas por Gradle cuando intenta actualizar tus dependencias no interfieran en los datos de la generación de perfiles. Para poder asegurarte de que Gradle haya descargado y almacenado en caché tus dependencias, es necesario que hayas compilado tu proyecto una vez.
    • --rerun-tasks: Hace que Gradle vuelva a ejecutar todas las tareas e ignore las optimizaciones de tareas.
  4. Figura 1: Vista del proyecto que indica la ubicación de los informes del perfil

    Una vez que finalice la compilación, usa la ventana Project para acceder al directorio project-root/build/reports/profile/ (como se muestra en la figura 1).

  5. Haz clic con el botón derecho en el archivo profile-timestamp.html y selecciona Open in Browser > Default. El informe debería ser similar al que se muestra en la figura 2. Puedes inspeccionar cada pestaña del informe para obtener información sobre tu compilación. Por ejemplo, en la pestaña Task Execution se muestra el tiempo que Gradle tardó en ejecutar cada tarea de compilación.

    Figura 2: Visualización de un informe en un navegador

  6. Opcional: Antes de realizar cambios en la configuración de tu proyecto o compilación, repite el comando del paso 3, pero omite la marca --rerun-tasks. Debido a que Gradle intenta ahorrar tiempo no volviendo a ejecutar tareas cuyas entradas no cambiaron (se indican como UP-TO-DATE en la pestaña Task Execution del informe, como se muestra en la figura 3), puedes identificar las tareas en ejecución que no deberían estar activas. Por ejemplo, si no se marca :app:processDevUniversalDebugManifest como UP-TO-DATE, esto puede sugerir que la configuración de tu compilación actualiza el manifiesto de forma dinámica con cada compilación. No obstante, algunas tareas se deben ejecutar durante cada compilación, como :app:checkDevDebugManifest.

    Figura 3: Visualización de los resultados de ejecución de tareas

Ahora que tienes un informe de perfil de compilación, puedes inspeccionar la información de cada pestaña del informe para comenzar a buscar oportunidades de optimización. Para algunas configuraciones de la compilación, se requiere experimentación, ya que los beneficios pueden diferir entre proyectos y estaciones de trabajo. Por ejemplo, en proyectos con una gran base de código, es posible aprovechar la reducción de código a fin de quitar el código no utilizado y reducir el tamaño de la app. No obstante, es posible lograr más beneficios para proyectos más pequeños si se inhabilita el código por completo. Además, aumentar el tamaño del montón de Gradle (mediante org.gradle.jvmargs) puede afectar negativamente el rendimiento en equipos con poca memoria.

Después de realizar un cambio en la configuración de tu compilación, observa los resultados de este repitiendo los pasos anteriores y generando un nuevo perfil de compilación. Por ejemplo, en la figura 4 se muestra un informe para el mismo ejemplo de app después de aplicar algunas de las optimizaciones básicas descritas en esta página.

Figura 4: Visualización de un informe nuevo después de optimizar la velocidad de compilación