Профилирование сборки

В крупных проектах или проектах с большим количеством пользовательской логики сборки может потребоваться более глубокий анализ процесса сборки для поиска узких мест. Для этого можно профилировать время, которое требуется Gradle для выполнения каждого этапа жизненного цикла сборки и каждой задачи сборки. Например, если в профиле сборки указано, что Gradle тратит слишком много времени на настройку проекта, возможно, вам нужно переместить пользовательскую логику сборки из фазы настройки. Кроме того, если задача mergeDevDebugResources занимает большую часть времени сборки, возможно, вам нужно преобразовать изображения в формат WebP или отключить сжатие PNG.

Если вы используете Android Studio 4.0 или более поздней версии, то для выявления проблем с производительностью сборки лучше всего использовать Build Analyzer.

Кроме того, есть два способа профилирования сборки за пределами Android Studio:

  1. Отдельный инструмент gradle-profiler, который позволяет проводить глубокий анализ сборки.

  2. Параметр Gradle --profile – удобный инструмент, доступный в командной строке Gradle.

Как использовать отдельный инструмент gradle-profiler

Чтобы найти конфигурацию проекта, которая обеспечивает наилучшую скорость сборки, используйте профилировщик Gradle – инструмент для сбора информации о профилировании и тестировании сборок Gradle. Профилировщик Gradle позволяет создавать сценарии сборки и запускать их несколько раз, предотвращая сильное расхождение между результатами и обеспечивая их воспроизводимость.

Режим тестирования следует использовать для сбора информации о чистых и инкрементных сборках, а режим профилирования – для сбора более подробной информации о запусках, включая снимки ЦП.

Некоторые из конфигураций настройки проекта для сравнительного анализа включают:

  • Версии плагина
  • Версии Gradle
  • Настройки JVM (размер кучи, размер PermGen, сборка мусора и т. д.)
  • Количество рабочих процессов Gradle (org.gradle.workers.max)
  • Настройки плагинов для дальнейшей оптимизации производительности.

Начало работы

  • Установите gradle-profiler, следуя этим инструкциям.
  • Бег: gradle-profiler --benchmark --project-dir <root-project> :app:assembleDebug

Это позволит протестировать полностью обновленную сборку, поскольку --benchmark выполняет задачу несколько раз, не меняя проект между запусками. Затем в каталоге profile-out/ будет создан HTML-отчет с указанием времени сборки.

Есть и другие сценарии, которые могут быть более полезными для тестирования:

  • Изменения кода в теле метода в классе, где вы выполняете большую часть работы.
  • Изменения API в модуле, который используется во всем проекте. Хотя это происходит реже, чем изменения в вашем собственном коде, это оказывает большее влияние, и полезно его измерить.
  • Изменения макета для имитации итерации работы над пользовательским интерфейсом.
  • Изменения строк для имитации работы с переводом.
  • Очистка сборок для симуляции изменений в самой сборке (например, обновление плагина Android Gradle, обновление Gradle или изменение собственного кода сборки в buildSrc).

Чтобы оценить эти сценарии использования, вы можете создать сценарий, который будет использоваться для выполнения gradle-profiler и который применяет соответствующие изменения к вашим источникам. Ниже описаны некоторые распространенные сценарии.

Профилирование различных настроек памяти/ЦП

Чтобы сравнить разные настройки памяти и ЦП, можно создать несколько сценариев с разными значениями org.gradle.jvmargs. Например, можно создать сценарии:

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

При запуске gradle-profiler --benchmark --project-dir <root-project> --scenario-file scenarios.txt будут выполнены три сценария, и вы сможете сравнить, сколько времени занимает :app:assembleDebug для каждой из этих настроек.

Профилирование разных версий плагина Gradle

Чтобы узнать, как изменение версии плагина Gradle влияет на время сборки, создайте сценарий для тестирования. Для этого требуется некоторая подготовка, чтобы версию плагина можно было внедрить из сценария. Измените корневой файл 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"
    }
}

Теперь вы можете указать версии плагинов Android Gradle и Kotlin Gradle в файле сценария и добавить в сценарий новый метод для исходных файлов:

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

Профилирование инкрементной сборки

Большинство сборок являются инкрементными, что делает этот сценарий одним из наиболее важных для профилирования. Gradle Profiler поддерживает профилирование инкрементных сборок. Она может автоматически применять изменения к исходному файлу, изменяя тело метода, добавляя новый метод или изменяя макет или строковый ресурс. Например, вы можете создать такие сценарии:

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

Выполнение команды gradle-profiler --benchmark --project-dir <root-project> --scenario-file scenarios.txt создаст HTML-отчет с данными тестирования.

Вы можете комбинировать инкрементные сценарии с другими настройками, такими как размер кучи, количество рабочих процессов или версия 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"]
}

Профилирование чистой сборки

Чтобы протестировать чистую сборку, вы можете создать сценарий, который будет использоваться для запуска gradle-profiler:

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

Чтобы запустить этот сценарий, используйте следующую команду:

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

Использование параметра Gradle --profile

Чтобы создать и посмотреть профиль сборки из командной строки Gradle, выполните следующие действия:

  1. Откройте терминал командной строки в корневом каталоге проекта.

  2. Выполните чистую сборку, введя следующую команду: При профилировании сборки следует выполнять чистую сборку между каждой сборкой, которую вы профилируете, поскольку Gradle пропускает задачи, когда входные данные для задачи (например, исходный код) не изменяются. Таким образом, вторая сборка без изменений входных данных всегда выполняется быстрее, поскольку задачи не выполняются повторно. Поэтому запуск задачи clean между сборками гарантирует, что вы профилируете весь процесс сборки.

    // On Mac or Linux, run the Gradle wrapper using "./gradlew".
    gradlew clean
    
  3. Выполните отладочную сборку одной из версий продукта, например dev, со следующими флагами:

    gradlew --profile --offline --rerun-tasks assemble<var>Flavor</var>Debug
    
    • --profile – включить профилирование.

    • --offline – запрещает Gradle получать онлайн-зависимости. Это гарантирует, что задержки, вызванные попытками Gradle обновить зависимости, не повлияют на данные профилирования. Вы должны были уже собрать свой проект один раз, чтобы убедиться, что Gradle уже скачал и кэшировал ваши зависимости.

    • --rerun-tasks – принудительно перезапускает все задачи Gradle и игнорирует оптимизацию задач.

  4. После завершения сборки перейдите в каталог project-root/build/reports/profile/ в окне Project (Проект), как показано на рисунке 1.

    Рисунок 1. Окно проекта с указанием расположения отчетов о профилях.

  5. Нажмите правой кнопкой мыши на файл profile-timestamp.html и выберите Open in Browser (Открыть в браузере) > Default (По умолчанию). Отчет должен выглядеть примерно так, как показано на рисунке 2. Вы можете изучить каждую вкладку в отчете, чтобы узнать о своей сборке, например вкладку Task Execution, на которой показано, сколько времени потребовалось Gradle для выполнения каждой задачи сборки.

    Рисунок 2. просмотр отчета в браузере;

  6. Необязательно. Прежде чем вносить изменения в проект или конфигурацию сборки, повторите команду из шага 3, но без флага --rerun-tasks. Gradle пытается сэкономить время, не выполняя повторно задачи, входные данные которых не изменились (они помечаются как UP-TO-DATE на вкладке Task Execution (Выполнение задач) отчета, как показано на рисунке 3). Это позволяет определить, какие задачи выполняются, когда не должны. Например, если :app:processDevUniversalDebugManifest не помечен как UP-TO-DATE, это может означать, что конфигурация сборки динамически обновляет манифест при каждой сборке. Однако некоторые задачи, например :app:checkDevDebugManifest, должны выполняться при каждой сборке.

    Рисунок 3. Просмотр результатов выполнения задач.

Теперь, когда у вас есть отчет о профиле сборки, вы можете начать искать возможности для оптимизации, изучая информацию на каждой вкладке отчета. Некоторые настройки сборки требуют экспериментов, поскольку преимущества могут различаться в зависимости от проекта и рабочей станции. Например, для проектов с большой базой кода может быть полезно сокращение кода, которое позволяет удалить неиспользуемый код и уменьшить размер приложения. Однако в небольших проектах может быть полезно полностью отключить сокращение кода. Кроме того, увеличение размера кучи Gradle (с помощью org.gradle.jvmargs) может отрицательно повлиять на производительность на компьютерах с небольшим объемом памяти.

После изменения конфигурации сборки посмотрите на результаты, повторив предыдущие шаги и создав новый профиль сборки. Например, на рисунке 4 показан отчет для того же примера приложения после применения некоторых базовых оптимизаций, описанных на этой странице.

Рисунок 4. Просмотр нового отчета после оптимизации скорости сборки.