Profila la tua build

I progetti più grandi o quelli che implementano molta logica di build personalizzata potrebbero richiedere un'analisi più approfondita del processo di build per trovare i colli di bottiglia. Puoi farlo profilando il tempo impiegato da Gradle per eseguire ogni fase del ciclo di vita della build e ogni attività di build. Ad esempio, se il profilo di build mostra che Gradle impiega troppo tempo per configurare il progetto, potrebbe suggerire che devi spostare la logica di build personalizzata fuori dalla fase di configurazione. Inoltre, se l'attività mergeDevDebugResources richiede molto tempo per la build, potrebbe essere necessario convertire le immagini in WebP o disattivare la compressione PNG.

Se utilizzi Android Studio 4.0 o versioni successive, il modo migliore per esaminare i problemi di prestazioni della build è utilizzare Build Analyzer.

Inoltre, esistono due opzioni per profilare la build al di fuori di Android Studio:

  1. Lo strumento gradle-profiler autonomo, uno strumento efficace per l'analisi approfondita della build.

  2. L'opzione --profile di Gradle, un comodo strumento disponibile dalla riga di comando di Gradle.

Utilizzo dello strumento gradle-profiler autonomo

Per trovare la configurazione del progetto che ti offre la migliore velocità di build, devi utilizzare Gradle Profiler, uno strumento per raccogliere informazioni di profilazione e benchmark per le build Gradle. Gradle Profiler ti consente di creare scenari di build ed eseguirli più volte, evitando un'elevata varianza tra i risultati e garantendo la riproducibilità dei risultati.

La modalità di benchmarking deve essere utilizzata per raccogliere informazioni sulle build pulite e incrementali, mentre la modalità di profilazione può essere utilizzata per raccogliere informazioni più granulari sulle esecuzioni, inclusi gli snapshot della CPU.

Alcune delle configurazioni di configurazione del progetto per il benchmarking includono:

  • Versioni del plug-in
  • Versioni di Gradle
  • Impostazioni JVM (dimensioni heap, dimensioni permgen, garbage collection e così via)
  • Numero di worker Gradle (org.gradle.workers.max)
  • Opzioni per singolo plug-in per ottimizzare ulteriormente le prestazioni

Per iniziare

  • Installa gradle-profiler seguendo queste istruzioni
  • Esegui: gradle-profiler --benchmark --project-dir <root-project> :app:assembleDebug

In questo modo verrà eseguito il benchmark di una build completamente aggiornata, perché --benchmark esegue l'attività più volte senza modificare il progetto nel frattempo. Poi genererà un report HTML nella directory profile-out/ che mostra i tempi di build.

Esistono altri scenari che potrebbero essere più utili per il benchmark:

  • Modifiche al codice nel corpo di un metodo in una classe in cui svolgi la maggior parte del tuo lavoro.
  • Modifiche all'API in un modulo utilizzato in tutto il progetto. Sebbene siano meno frequenti delle modifiche al tuo codice, hanno un impatto maggiore ed è utile misurarle.
  • Modifiche al layout per simulare l'iterazione del lavoro sulla UI.
  • Modifiche alle stringhe per simulare la gestione del lavoro di traduzione.
  • Build pulite per simulare le modifiche alla build stessa (ad es. aggiornamento del plug-in Android per Gradle, aggiornamento di Gradle o modifiche al codice della build in buildSrc).

Per eseguire il benchmark di questi casi d'uso, puoi creare uno scenario che verrà utilizzato per guidare l'esecuzione di gradle-profiler e che applica le modifiche appropriate alle tue origini. Le sezioni seguenti descrivono alcuni scenari comuni.

Profilazione di diverse impostazioni di memoria/CPU

Per eseguire il benchmark di diverse impostazioni di memoria e CPU, puoi creare più scenari che utilizzano valori diversi per org.gradle.jvmargs. Ad esempio, puoi creare scenari:

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

L'esecuzione di gradle-profiler --benchmark --project-dir <root-project> --scenario-file scenarios.txt eseguirà tre scenari e potrai confrontare il tempo impiegato da :app:assembleDebug per ciascuna di queste configurazioni.

Profilazione di diverse versioni del plug-in Gradle

Per scoprire in che modo la modifica della versione del plug-in Gradle influisce sui tempi di build, crea uno scenario per il benchmarking. Ciò richiede una certa preparazione per rendere la versione del plug-in inseribile dallo scenario. Modifica il file build.gradle principale:

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

Ora puoi specificare le versioni del plug-in Android per Gradle e del plug-in Kotlin per Gradle dal file degli scenari e fare in modo che lo scenario aggiunga un nuovo metodo ai file di origine:

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

Profilazione di una build incrementale

La maggior parte delle build è incrementale, il che rende questo uno degli scenari più importanti da profilare. Gradle Profiler offre un ampio supporto per la profilazione delle build incrementali. È in grado di applicare automaticamente le modifiche a un file di origine modificando il corpo di un metodo, aggiungendo un nuovo metodo o modificando un layout o una risorsa stringa. Ad esempio, puoi creare scenari incrementali come questo:

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

L'esecuzione di gradle-profiler --benchmark --project-dir <root-project> --scenario-file scenarios.txt genererà il report HTML con i dati di benchmark.

Puoi combinare gli scenari incrementali con altre impostazioni, come le dimensioni dell'heap, il numero di worker o la versione di 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"]
}

Profilazione di una build pulita

Per eseguire il benchmark di una build pulita, puoi creare uno scenario che verrà utilizzato per guidare l'esecuzione di gradle-profiler:

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

Per eseguire questo scenario, utilizza il seguente comando:

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

Utilizzo dell'opzione Gradle --profile

Per generare e visualizzare un profilo di build dalla riga di comando di Gradle, segui questi passaggi:

  1. Apri un terminale a riga di comando nella directory root del progetto.

  2. Esegui una build pulita inserendo il seguente comando. Quando profili la build, devi eseguire una build pulita tra ogni build che profili, perché Gradle salta le attività quando gli input di un'attività (come il codice sorgente) non cambiano. Pertanto, una seconda build senza modifiche agli input viene sempre eseguita più velocemente perché le attività non vengono eseguite di nuovo. Pertanto, l'esecuzione dell'attività clean tra le build garantisce la profilazione dell'intero processo di build.

    // On Mac or Linux, run the Gradle wrapper using "./gradlew".
    gradlew clean
    
  3. Esegui una build di debug di una delle tue versioni prodotto, ad esempio la versione "dev", con i seguenti flag:

    gradlew --profile --offline --rerun-tasks assemble<var>Flavor</var>Debug
    
    • --profile: attiva la profilazione.

    • --offline: impedisce a Gradle di recuperare le dipendenze online. In questo modo, eventuali ritardi causati dal tentativo di Gradle di aggiornare le dipendenze non interferiscono con i dati di profilazione. Dovresti aver già creato il progetto una volta per assicurarti che Gradle abbia già scaricato e memorizzato nella cache le dipendenze.

    • --rerun-tasks: forza Gradle a eseguire nuovamente tutte le attività e a ignorare eventuali ottimizzazioni delle attività.

  4. Al termine della build, utilizza la finestra Progetto per accedere alla directory project-root/build/reports/profile/ (come mostrato nella figura 1).

    Visualizzazione del progetto Android Studio che mostra i report del profilo HTML generati in build/reports/profile.
    Figura 1. La visualizzazione Progetto indica la posizione dei report del profilo.
  5. Fai clic con il tasto destro del mouse sul file profile-timestamp.html e seleziona Apri nel browser > Predefinito. Il report dovrebbe essere simile a quello mostrato nella Figura 2. Puoi esaminare ogni scheda del report per scoprire di più sulla tua build, ad esempio la scheda Esecuzione attività, che mostra il tempo impiegato da Gradle per eseguire ogni attività di build.

    Crea un report del profilo in un browser che mostra un tempo di build di riferimento di 46,567 secondi.
    Figura 2. Un report sul profilo di build si apre in un browser.
  6. Facoltativo: prima di apportare modifiche al progetto o alla configurazione di build, ripeti il comando nel passaggio 3, ma ometti il flag --rerun-tasks. Poiché Gradle tenta di risparmiare tempo non eseguendo nuovamente le attività i cui input non sono stati modificati, la scheda Esecuzione attività del report contrassegna le attività invariate come UP-TO-DATE, come mostrato nella figura 3. In questo modo puoi identificare le attività che vengono eseguite quando non dovrebbero. Ad esempio, se l'attività :app:processDevUniversalDebugManifest non è contrassegnata come UP-TO-DATE, potrebbe suggerire che la configurazione di compilazione aggiorna dinamicamente il manifest con ogni build. Tuttavia, alcune attività devono essere eseguite durante ogni build, ad esempio :app:checkDevDebugManifest.

    Scheda Esecuzione attività nel report del profilo di build che mostra le attività dell'app contrassegnate come aggiornate con un tempo di build totale di 0,280 s.
    Figura 3. La scheda Esecuzione attività mostra i risultati dell'esecuzione delle attività.

Ora che hai un report sul profilo di build, puoi iniziare a cercare opportunità di ottimizzazione esaminando le informazioni in ogni scheda del report. Alcune impostazioni di build richiedono sperimentazione perché i vantaggi potrebbero variare tra progetti e workstation. Ad esempio, i progetti con un codebase di grandi dimensioni potrebbero trarre vantaggio dalla riduzione del codice per rimuovere il codice inutilizzato e ridurre le dimensioni dell'app. Tuttavia, i progetti più piccoli potrebbero trarre maggiori vantaggi dalla disattivazione completa della riduzione del codice. Inoltre, l'aumento delle dimensioni dell'heap di Gradle (utilizzando org.gradle.jvmargs) potrebbe influire negativamente sulle prestazioni su macchine con memoria ridotta.

Dopo aver apportato una modifica alla configurazione di compilazione, osserva i risultati delle modifiche ripetendo i passaggi precedenti e generando un nuovo profilo di build. Ad esempio, la figura 4 mostra un report per la stessa app di esempio dopo l'applicazione di alcune delle ottimizzazioni di base descritte in questo documento.

Report del profilo di build che mostra un tempo di build post-ottimizzazione di 29,668 secondi,
      in calo rispetto ai 46,567 secondi della figura 2.
Figura 4. Un report sul profilo di build mostra tempi di build più rapidi dopo l'ottimizzazione della velocità di build.