Criar perfil para sua compilação

Projetos maiores ou aqueles que implementam uma grande quantidade de lógica de compilação personalizada podem exigir que você examine mais detalhadamente o processo de compilação para encontrar gargalos. Você pode fazer isso analisando quanto tempo leva para o Gradle executar cada fase do ciclo de vida e cada tarefa de compilação. Por exemplo, se o perfil da compilação mostra que o Gradle passa tempo demais configurando o projeto, isso pode sugerir que é preciso retirar a lógica de compilação personalizada da fase de configuração. Além disso, se a tarefa mergeDevDebugResources consome uma grande parte do tempo de compilação, isso pode indicar que você precisa converter suas imagens em WebP ou desativar a análise de PNG.

Se você está usando o Android Studio 4.0 ou versão mais recente, a melhor maneira de investigar problemas de desempenho de compilação é usar o Build Analyzer.

Além disso, há duas opções de criar perfil do build fora do Android Studio:

  1. A ferramenta gradle-profiler autônoma, uma ferramenta robusta para análise detalhada do seu build.

  2. A opção --profile do Gradle, uma ferramenta conveniente, disponível da linha de comando do Gradle.

Como usar a ferramenta gradle-profiler autônoma

Para encontrar a configuração de projeto com a melhor velocidade de compilação, você pode usar o Gradle profiler, uma ferramenta para coleta de informações sobre criação de perfis e comparações para builds do Gradle. O Gradle profiler permite criar cenários de compilação e executá-los múltiplas vezes, evitando grande variação entre resultados e garantindo a reprodução dos resultados.

O Modo de comparação é usado para coletar informações sobre builds limpos e incrementais, enquanto o modo de criação de perfil pode ser usado para coletar informações granulares sobre a execução, incluindo snapshots da CPU.

Algumas das configuração de projeto para comparação incluem:

  • Versões do plug-in
  • Versões do Gradle
  • Configurações de JVM (tamanho da alocação heap, tamanho de permgen, coleta de lixo etc.)
  • Número de workers do Gradle (org.gradle.workers.max)
  • Opções por plug-in para otimizar ainda mais o desempenho

Primeiros passos

  • Instale o gradle-profiler seguindo estas instruções
  • Execute: gradle-profiler --benchmark --project-dir <root-project> :app:assembleDebug

Isso vai comparar um build totalmente atualizado, porque o --benchmark executa a tarefa múltiplas vezes sem mudar o projeto entre elas. Depois, é gerado um relatório HTML sob o diretório profile-out/, mostrando os tempos de compilação.

Há outros cenários que podem ser mais úteis para comparar:

  • Mudanças de código no corpo de um método em uma classe em que você faz a maior parte do trabalho.
  • Mudanças de API em um módulo usado em todo o projeto. Embora seja menos frequente que as alterações do código, isso tem um impacto maior e é útil para avaliações.
  • Edições de layout para simular a iteração no trabalho da IU.
  • Edições de string para simular o trabalho com translação.
  • Builds limpos para simular mudanças na próprio build (por exemplo, atualização do Plug-in do Android para Gradle, atualização do Gradle ou edições no seu código de compilação em buildSrc).

Para comparar esses casos de uso, crie um cenário que será usado para impulsionar a execução do gradle-profiler e que aplica as mudanças adequadas às suas fontes. Veja alguns dos cenários comuns abaixo.

Como criar perfis com diferentes configurações de memória/CPU

Para comparar diferentes configurações de CPU e memória, é possível criar vários cenários que usam valores diferentes para org.gradle.jvmargs. Por exemplo, você pode criar cenários:

# <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"]
}

A execução de gradle-profiler --benchmark --project-dir <root-project> --scenario-file scenarios.txt executará três cenários, e será possível comparar o tempo de :app:assembleDebug em cada uma dessas configurações.

Como criar perfis de diferentes versões do plug-in Gradle

Para descobrir como a mudança da versão do plug-in Gradle afeta os tempos de compilação, crie um cenário para fins de comparação. Isso requer alguma preparação para tornar a versão do plug-in injetável a partir do cenário. Mude seu build.gradle raiz:

# <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"
    }
}

Agora você pode especificar as versões do Plug-in do Android para Gradle e do Kotlin para Gradle no arquivo de cenários e adicionar um novo método aos arquivos de origem:

# <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"
}

Como criar perfis para um build incremental

A maioria dos builds é incremental, o que torna esse um dos cenários mais importantes para o perfil. O Gradle profiler tem ampla compatibilidade com a criação de perfis de builds incrementais. Ele pode aplicar mudanças a um arquivo de origem automaticamente mudando um corpo de método, adicionando um novo método ou mudando um recurso de layout ou string. Por exemplo, é possível criar cenários incrementais como estes:

# <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"
}

A execução de gradle-profiler --benchmark --project-dir &lt;root-project> --scenario-file scenarios.txt irá gerar o relatório HTML com os dados de comparação.

É possível combinar cenários incrementais com outras configurações, como tamanho da alocação de heap, número de workers ou versão do 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"]
}

Como criar perfis para um build limpo

Para comparar um build limpo, você pode criar um cenário que será usado para conduzir a execução do gradle-profiler:

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

Para executar esse cenário, use o seguinte comando:

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

Como usar a opção --profile do Gradle

Para gerar e ver um perfil de compilação na linha de comando do Gradle, execute as seguintes etapas:

  1. Abra um terminal de linha de comando na raiz do projeto.
  2. Execute um build limpo inserindo o seguinte comando. Ao criar perfis para seu build, faça um build limpo entre cada build para o qual você criar um perfil, porque o Gradle pula tarefas quando entradas para uma delas (como o código-fonte) não mudam. Dessa forma, um segundo build sem mudanças nas entradas sempre será executado mais rapidamente, porque as tarefas não estão sendo repetidas. Portanto, executar a tarefa clean entre os builds garante que o perfil seja feito para todo o processo de compilação.
    // On Mac or Linux, run the Gradle wrapper using "./gradlew".
    gradlew clean
    
  3. Execute um build de depuração de uma das suas variações de produto, como a variação “dev”, com as seguintes sinalizações:
    gradlew --profile --offline --rerun-tasks assembleFlavorDebug
    
    • --profile: ativa a criação de perfis.
    • --offline: impede o Gradle de buscar dependências on-line. Isso garante que qualquer atraso causado pelo Gradle ao tentar atualizar as dependências não interfira nos dados de perfil. Você precisa ter criado o projeto uma vez para garantir que o Gradle já tenha feito o download das dependências e as armazenado em cache.
    • --rerun-tasks: força o Gradle a executar todas as tarefas novamente e ignorar qualquer otimização de tarefas.
  4. Figura 1. Visualização do projeto indicando a localização dos relatórios de perfil.

    Quando a criação for concluída, use a janela Project para navegar até o diretório project-root/build/reports/profile/ (conforme mostrado na Figura 1).

  5. Com o botão direito do mouse, clique no arquivo profile-timestamp.html e selecione Open in Browser > Default. O relatório será semelhante ao mostrado na Figura 2. Você pode inspecionar cada guia do relatório para aprender sobre seu build, como a guia Task Execution, que mostra quanto tempo o Gradle levou para executar cada tarefa de compilação.

    Figura 2. Visualização de um relatório em um navegador.

  6. Opcional: antes de fazer qualquer alteração no projeto ou na configuração da compilação, repita o comando na etapa 3, mas omita a sinalização --rerun-tasks. Como o Gradle tenta poupar tempo evitando repetir a execução de tarefas cujas entradas não mudaram (indicadas como UP-TO-DATE na guia Task Execution do relatório, conforme mostrado na Figura 3), você pode identificar quais tarefas estão trabalhando quando não deveriam estar. Por exemplo, se o :app:processDevUniversalDebugManifest não estiver marcado como UP-TO-DATE, pode ser indício de que a configuração da sua compilação atualiza o manifesto dinamicamente com cada build. Entretanto, algumas tarefas precisam ser executadas em todos os builds, como :app:checkDevDebugManifest.

    Figura 3. Visualização dos resultados da execução da tarefa.

Agora que você tem um relatório de perfil de compilação, pode começar a buscar oportunidades de otimização inspecionando as informações em cada guia do relatório. Algumas configurações de compilação exigem experimentação, porque os benefícios podem variar entre projetos e estações de trabalho. Por exemplo, projetos com uma grande base de códigos podem se beneficiar da redução de código para remover códigos não utilizados e reduzir o tamanho do app. Entretanto, para projetos menores, talvez seja mais vantajoso desativar a redução de código. Além disso, aumentar o tamanho de heap do Gradle (usando org.gradle.jvmargs) pode afetar negativamente o desempenho em máquinas com pouca memória.

Após fazer uma alteração na configuração do build, observe os resultados das suas alterações repetindo as etapas acima e gerando um novo perfil de compilação. Por exemplo, a Figura 4 mostra um relatório para o mesmo exemplo de app após aplicar algumas das otimizações básicas descritas nesta página.

Figura 4. Visualização de um novo relatório após otimizar a velocidade de compilação.