Profilowanie kompilacji

Większe projekty lub takie, które implementują dużo niestandardowych logiki kompilacji, mogą wymagać głębszego przyjrzenia się procesowi kompilacji w celu znalezienia wąskich gardeł. Możesz to zrobić, profilując czas potrzebny Gradle na wykonywanie każdej fazy cyklu życia kompilacji i każdego zadania kompilacji. Jeśli na przykład w profilu kompilacji widać, że Gradle poświęca zbyt dużo czasu na konfigurowanie projektu, może być konieczne przeniesienie niestandardowej logiki kompilacji z fazy konfiguracji. Poza tym, jeśli zadanie mergeDevDebugResources pochłania dużą ilość czasu kompilacji, może to oznaczać, że musisz przekonwertować obrazy do formatu WebP lub wyłączyć tworzenie pliku PNG.

Jeśli używasz Androida Studio w wersji 4.0 lub nowszej, najlepszym sposobem zbadania problemów z wydajnością kompilacji jest użycie Analizatora kompilacji.

Dodatkowo poza Android Studio dostępne są 2 opcje profilowania kompilacji:

  1. Samodzielne narzędzie gradle-profiler, wszechstronne narzędzie do szczegółowej analizy kompilacji.

  2. Opcja --profile Gradle – wygodne narzędzie dostępne z poziomu wiersza poleceń Gradle.

Korzystanie z samodzielnego narzędzia gradle-profiler

Aby znaleźć konfigurację projektu, która zapewnia największą szybkość kompilacji, użyj narzędzia do profilowania Gradle, które służy do gromadzenia informacji o profilowaniu i analiz porównawczych pod kątem kompilacji Gradle. Program profilujący Gradle umożliwia tworzenie scenariuszy kompilacji i uruchamianie ich wielokrotnie, co zapobiega dużej różnicy między wynikami i zapewnia ich powtarzalność.

Tryb analizy porównawczej powinien służyć do zbierania informacji o czystych i przyrostowych kompilacjach, a tryb profilowania może służyć do zbierania bardziej szczegółowych informacji o uruchomieniach, w tym zrzutów procesora.

Oto niektóre konfiguracje konfiguracji projektu na potrzeby testów porównawczych:

  • Wersje wtyczki
  • Wersje Gradle
  • Ustawienia JVM (rozmiar sterty, rozmiar instancji permgen, czyszczenie pamięci itp.)
  • Liczba instancji roboczych Gradle (org.gradle.workers.max)
  • Opcje dla poszczególnych wtyczek w celu dalszej optymalizacji wydajności

Wprowadzenie

  • Zainstaluj narzędzie Gradle-profiler, postępując zgodnie z tymi instrukcjami.
  • Uruchomienie: gradle-profiler --benchmark --project-dir <root-project> :app:assembleDebug

Będzie to test porównawczy z w pełni aktualną kompilacją, ponieważ --benchmark uruchamia to zadanie wiele razy, nie zmieniając projektu w międzyczasie. Następnie w katalogu profile-out/ wygeneruje raport HTML z czasami kompilacji.

Są też inne scenariusze, które mogą być bardziej przydatne do analizy porównawczej:

  • Kod zmienia się w treści metody w klasie, w której wykonujesz większość pracy.
  • zmiany interfejsu API w module, który jest używany w całym projekcie. Choć zmiany są wprowadzane rzadziej niż własne, ma to większy wpływ i warto je mierzyć.
  • Wprowadzanie zmian w układzie w celu symulowania iteracji pracy interfejsu.
  • Wprowadzanie zmian w ciągu znaków w celu symulowania pracy z tłumaczeniem.
  • Czyste kompilacje, które symulują zmiany w samej kompilacji (np. Aktualizacja wtyczki Androida do obsługi Gradle, aktualizacja Gradle lub zmiany własnego kodu kompilacji w buildSrc.

Aby porównać te przypadki użycia, możesz utworzyć scenariusz, który zostanie wykorzystany do wykonania instrukcji gradle-profiler i zastosuje odpowiednie zmiany do źródeł. Poniżej znajdziesz kilka typowych scenariuszy.

Profilowanie różnych ustawień pamięci/procesora

Aby porównać różne ustawienia pamięci i procesora, możesz utworzyć kilka scenariuszy z różnymi wartościami dla funkcji org.gradle.jvmargs. Możesz na przykład utworzyć scenariusze:

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

Uruchomienie polecenia gradle-profiler --benchmark --project-dir <root-project> --scenario-file scenarios.txt uruchomi 3 scenariusze i będziesz mieć możliwość porównania czasu, jaki zajmuje :app:assembleDebug w każdej z nich.

Profilowanie różnych wersji wtyczki Gradle

Aby dowiedzieć się, jak zmiana wersji wtyczki Gradle wpływa na czas kompilacji, utwórz scenariusz z testami porównawczymi. Wymaga to przygotowania do sytuacji, w której możliwe jest wstrzyknięcie wersji wtyczki. Zmień plik główny 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"
    }
}

Teraz w pliku scenariusza możesz określić wersje wtyczki Androida do obsługi Gradle i wtyczki Kotlin Gradle, a także dodać nową metodę do plików źródłowych:

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

Profilowanie kompilacji przyrostowej

Większość kompilacji ma charakter przyrostowy, dlatego jest to jeden z najważniejszych scenariuszy profilowania. Narzędzie do profilowania Gradle oferuje rozbudowane wsparcie do tworzenia przyrostowych kompilacji. Może automatycznie wprowadzać zmiany w pliku źródłowym, zmieniając treść metody, dodając nową metodę lub zmieniając układ bądź zasób ciągu znaków. Możesz na przykład utworzyć dodatkowe scenariusze w następujący sposób:

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

Uruchomienie funkcji gradle-profiler --benchmark --project-dir &lt;root-project> --scenario-file scenarios.txt spowoduje wygenerowanie raportu HTML z danymi analizy porównawczej.

Możesz połączyć scenariusze przyrostowe z innymi ustawieniami, takimi jak rozmiar sterty, liczba instancji roboczych lub wersja 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"]
}

Profiluję nienaruszoną kompilację

Aby porównać czystą kompilację, możesz utworzyć scenariusz, który zostanie użyty do przeprowadzenia wykonania narzędzia Gradle-profiler:

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

Aby uruchomić ten scenariusz, użyj tego polecenia:

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

Używanie opcji Gradle --profile

Aby wygenerować i wyświetlić profil kompilacji z poziomu wiersza poleceń Gradle, wykonaj te czynności:

  1. Otwórz terminal wiersza poleceń w katalogu głównym projektu.
  2. Aby wykonać czystą kompilację, wpisz to polecenie. Profiluj kompilację należy wykonywać między poszczególnymi profilami kompilacji, ponieważ Gradle pomija zadania, gdy dane wejściowe zadania (np. kod źródłowy) nie ulegną zmianie. W związku z tym druga kompilacja bez zmian w danych wejściowych zawsze działa szybciej, ponieważ zadania nie są ponownie uruchamiane. Dlatego uruchomienie zadania clean między kompilacjami zapewni profilowanie całego procesu kompilacji.
    // On Mac or Linux, run the Gradle wrapper using "./gradlew".
    gradlew clean
    
  3. Wykonaj kompilację do debugowania jednej z rodzajów usługi, na przykład typu „dev”, z tymi flagami:
    gradlew --profile --offline --rerun-tasks assembleFlavorDebug
    
    • --profile: włącza profilowanie.
    • --offline: wyłącza możliwość pobierania zależności online przez Gradle. Dzięki temu opóźnienia spowodowane próbą aktualizacji zależności przez Gradle nie kolidują z danymi profilowania. Musisz już skompilować projekt, aby mieć pewność, że Gradle pobrała już zależności i przechowuje je w pamięci podręcznej.
    • --rerun-tasks: wymusza ponowne uruchomienie wszystkich zadań przez Gradle i zignorowanie wszystkich optymalizacji zadań.
  4. Rysunek 1. Widok projektu wskazujący lokalizację raportów profilu.

    Po zakończeniu kompilacji w oknie Projekt przejdź do katalogu project-root/build/reports/profile/ (jak pokazano na rysunku 1).

  5. Kliknij prawym przyciskiem myszy plik profile-timestamp.html i wybierz Otwórz w przeglądarce > Domyślna. Raport powinien wyglądać podobnie do tego na ilustracji 2. Możesz sprawdzić każdą kartę w raporcie, aby poznać informacje o kompilacji, np. kartę Task Execution, która pokazuje, ile czasu zajęło Gradle na wykonanie poszczególnych zadań kompilacji.

    Rysunek 2. Wyświetlenie raportu w przeglądarce.

  6. Opcjonalnie: zanim wprowadzisz zmiany w projekcie lub konfiguracji kompilacji, powtórz polecenie podane w kroku 3, ale pomiń flagę --rerun-tasks. Ponieważ Gradle próbuje oszczędzać czas przez niepodejmowanie ponownego wykonywania zadań, których dane wejściowe się nie zmieniły (są one oznaczone jako UP-TO-DATE na karcie Wykonywanie zadań (jak widać na ilustracji 3), możesz więc określić, które zadania wykonują pracę, a które nie powinny. Jeśli na przykład :app:processDevUniversalDebugManifest nie jest oznaczony jako UP-TO-DATE, może to oznaczać, że konfiguracja kompilacji dynamicznie aktualizuje plik manifestu przy każdej kompilacji. Jednak niektóre zadania muszą być uruchamiane podczas każdej kompilacji, np. :app:checkDevDebugManifest.

    Rysunek 3. Wyświetlam wyniki wykonania zadań.

Po utworzeniu raportu profilu kompilacji możesz zacząć szukać możliwości optymalizacji, sprawdzając informacje na każdej karcie raportu. Niektóre ustawienia kompilacji wymagają eksperymentowania, ponieważ korzyści mogą się różnić w zależności od projektów i stacji roboczych. Na przykład w przypadku projektów z dużą bazą kodu korzystne może być zmniejszenie kodu, by usunąć nieużywany kod i zmniejszyć rozmiar aplikacji. Jednak całkowite wyłączenie ograniczania kodu może być bardziej korzystne w przypadku mniejszych projektów. Dodatkowo zwiększenie rozmiaru sterty Gradle (za pomocą org.gradle.jvmargs) może negatywnie wpłynąć na wydajność na maszynach z małą ilością pamięci.

Po wprowadzeniu zmian w konfiguracji kompilacji obserwuj wyniki wprowadzonych zmian, powtarzając powyższe kroki i generując nowy profil kompilacji. Na przykład rysunek 4 przedstawia raport dotyczący tej samej przykładowej aplikacji po zastosowaniu kilku podstawowych optymalizacji opisanych na tej stronie.

Rysunek 4. Wyświetlenie nowego raportu po zoptymalizowaniu szybkości kompilacji.