Разработка
Как DuckDuckGo сократила время сборки Android-приложений на 57%
Работая с командой Gradle, DuckDuckGo использовала скрипты проверки сборки Develocity, чтобы систематически выявлять и исправлять проблемы, связанные с производительностью.
Браузер DuckDuckGo для Android — один из наиболее активно поддерживаемых проектов с открытым исходным кодом для Android на GitHub. Команда часто выпускает обновления, рассматривает большое количество запросов на слияние и активно использует CI для поддержания темпа работы. Но, как и во многих крупных Android-проектах, в их сборках Gradle незаметно накапливались неэффективности — задачи выполнялись несколько раз, когда в этом не было необходимости, кэши незаметно аннулировались из-за особенностей конфигурации, и не было простого способа увидеть, куда на самом деле уходит время.
Проект охватывает 160 модулей с широкой областью компиляции — ассемблер, линтинг, модульные тесты JVM, инструментальные тесты Android, форматирование кода, обработка аннотаций и нативные сборки — все это находится в критическом пути.
Компания Gradle Technologies предоставляет DuckDuckGo бесплатный экземпляр Develocity в рамках поддержки сообщества открытого исходного кода, предоставляя команде доступ к анализу сборки из Build Scan, кэшированию сборки из Remote Build Cache и предотвращению выполнения тестов с помощью Predictive Test Selection. Помимо кэширования сборки, Develocity обеспечивает командам Android-разработчиков доступ к тем аспектам сборки, которые в противном случае остаются непрозрачными — анализ критического пути, трассировка преобразования артефактов, анализ времени конфигурации, диагностика промахов кэша конфигурации, отслеживание сбоев и нестабильности тестов, а также мониторинг использования ресурсов — все это автоматически отображается в каждом Build Scan, полученном для локальных и CI-сборок.
Работая с командой Gradle Technologies, DuckDuckGo использовала скрипты проверки сборки Develocity, чтобы систематически выявлять и исправлять проблемы, связанные с производительностью. Вот что они обнаружили — и чему могут научиться другие команды разработчиков Android.
Использование Build Validation Scripts для диагностики проблемы
Скрипты проверки сборки (Build Validation Scripts) представляют собой структурированную последовательность тестов, которые постепенно проверяют поведение кэширования сборки проекта. Каждый эксперимент основывается на предыдущем, сужая круг поиска мест, где работа повторяется без необходимости.
Эксперимент 1 — Инкрементальные сборки подтвердили корректность основных параметров. Время выполнения пересборки, не выполняющей никаких действий, сокращается с 5 минут 59 секунд до 50 секунд.
Эксперимент 2 — Локальное кэширование сборки также выглядело корректно. После чистой пересборки, например, при смене веток, время сборки сокращается с 5 минут 45 секунд до 1 минуты 36 секунд, при этом выполнялись только две незначительные задачи (в сумме 0,4 секунды).
Эксперимент 3 — Проблемы выявились в кэшировании сборки в разных местах проекта. Этот эксперимент подтверждает, что кэширование сборки по-прежнему обеспечивает полные попадания в кэш, когда проект находится в разных местах файловой системы, как это происходит, например, когда проект собирается агентом CI и разработчиком, или когда разработчик использует рабочие деревья Git — рабочий процесс, на который активно полагается команда DuckDuckGo. Он также закладывает основу для удаленного кэширования сборки, поскольку разные машины обычно имеют разные абсолютные пути к проекту. Время сборки сокращается с 6 минут 53 секунд до 2 минут 57 секунд, но 440 кэшируемых задач все еще выполнялись, что представляет собой 14 минут 44 секунды непрерывной работы, которая повторялась без необходимости.
Эксперимент 4 — Удаленное кэширование CI-to-CI измеряет, может ли агент CI повторно использовать результаты задач, созданных предыдущим запуском CI, через удаленный кэш сборки — рабочий процесс, который наиболее непосредственно влияет на время обратной связи по запросам на слияние, поскольку каждая сборка запроса на слияние начинается с «теплого» кэша, заполненного результатами других запусков CI. В ходе этого эксперимента было выявлено 13 задач с промахами кэша в процессе выполнения CI, что привело к затратам 4 минуты 48 секунд на последовательное выполнение — это небольшая величина в абсолютном выражении, но каждый повторяющийся промах усугубляет ситуацию при каждой сборке запроса на слияние (сравнение сборок).
Каждый эксперимент указывал на конкретные, устранимые первопричины.
Исправление недетерминированной генерации кода Dagger
Эксперимент 4 показал, что процессор аннотаций Dagger генерировал классы с недетерминированным порядком методов. Два запуска CI, обрабатывающие один и тот же исходный код, давали функционально идентичные, но различающиеся по байтам результаты. Поскольку кэш сборки Gradle использует хеширование содержимого, разный порядок байтов приводит к разным ключам кэша — и, следовательно, к промахам кэша для задач, которые обрабатывают байт-код.
Обновление Dagger до версии 2.53+ с детерминированным выводом решило эту проблему немедленно: 1619 задач, ранее пропустивших кэш, теперь попадают в кэш сборки, что экономит более 15 минут времени последовательного выполнения в рабочих процессах CI (сравнение сборок).
Разблокировка нативного кэширования сборки CMake
Проект DuckDuckGo включает нативный код, собранный с помощью CMake. Решение проблемы промахов кэша на этом уровне стало еще одним значительным достижением. После внесения этого исправления эксперимент 3 показал 3740 предотвращенных кэшируемых задач и общее сэкономленное время последовательного выполнения в 1 час 57 минут на каждый цикл чистой междоменной сборки (сравнение сборок).
Благодаря объединенным исправлениям Dagger и CMake, в эксперименте 4 теперь достигается нулевое количество выполнения кэшируемых задач между последовательными запусками CI — удаленный кэш сборки полностью прогрет (сравнение сборок).
Результаты
Продолжительность выполнения рабочего процесса CI (GitHub Actions)
Это время выполнения рабочего процесса от начала до конца, указанное в отчете GitHub Actions.
| Workflow | Before | After | Reduction |
|---|---|---|---|
| build-debug-apk.yml | 11m 30s | 6m 58s | 39% |
| ci.yml | 15m 00s | 12m 12s | 19% |
Улучшение в ci.yml меньше, потому что оно включает инструментальные тесты Android и тесты Fladle/Firebase, которые по своей природе не кэшируются. В части конвейера, где кэширование возможно, наблюдался гораздо больший относительный прирост.
Локальные сборки
| Scenario | Before | After | Reduction |
|---|---|---|---|
| Different project locations (exp 3) | 6m 53s | 2m 57s | 57% |
| CI-to-local assembly | 10m 38s | 45s | 93% |
Разработчики, регулярно использующие рабочие деревья Git, отмечают экономию 10–20 минут времени на сборку за счет избегания последовательных операций, что составляет примерно 2–4 минуты экономии по сравнению с фактическим временем.
Кэширование на уровне задач CI (Эксперимент 4)
Это сравнение времени выполнения отдельных задач Gradle в двух последовательных запусках CI, измеряющее эффективность удаленного кэша сборок.
| Gradle Task | Before | After | Reduction |
|---|---|---|---|
| spotlessCheck | 1m 57s | 50s | 57% |
| jvm_tests | 14m | 1m 52s | 87% |
| androidTestsBuild | 6m 35s | 3m 45s | 43% |
| lint | 10m 15s | 3m 24s | 67% |
Прогнозируемый выбор тестов и мониторинг тестов
Помимо работы над кэшированием, команда настроила повторные попытки выполнения модульных тестов и импорт XML-файлов JUnit для тестов, подключенных к Android, и тестов Fladle/Firebase. В сочетании с панелью мониторинга Test Analytics от Develocity это дает команде централизованное представление для отслеживания и исправления нестабильных и неработающих тестов во всем наборе тестов.
После улучшений кэширования DuckDuckGo включил прогнозируемый выбор тестов (PTS) для модульных тестов JVM. PTS использует машинное обучение для определения того, какие тесты, вероятно, затронуты изменением кода, и запускает только их, пропуская остальные.
Команда настроила PTS с помощью стандартного профиля Develocity, который обеспечивает баланс между скоростью и покрытием тестов. Он включен для локальных сборок и проверок запросов на слияние — где быстрая обратная связь наиболее важна — в то время как рабочие процессы после слияния и ночные рабочие процессы по-прежнему запускают полный набор тестов для обеспечения безопасности.
За последние 7 дней удалось сэкономить 20 часов и 33 минуты времени последовательного тестирования, что составляет 56% от общего времени выполнения тестов в сборках, где была активна функция PTS. Для проекта с тысячами модульных тестов, распределенных по десяткам модулей, это напрямую приводит к ускорению циклов обратной связи по PR и снижению затрат на CI.
Что могут почерпнуть другие команды разработчиков Android
Если что-то из этого вам знакомо, вот наиболее важные вещи, которые вы можете проверить в своем проекте:
Проверьте пути к схеме Room. Если вы определяете места экспорта схемы с помощью абсолютных путей, вы, вероятно, аннулируете кэш сборки на разных машинах. Перейдите на плагин Room Gradle, который автоматически обрабатывает относительные пути к схеме — это, вероятно, самая распространенная проблема, приводящая к нарушению кэширования в проектах Android.
Используйте версии обработчиков аннотаций с детерминированным выводом. Dagger и другие обработчики аннотаций исторически выдавали недетерминированный порядок вывода. Если вы видите необъяснимые промахи кэша на разных машинах в задачах kapt или ksp, попробуйте обновиться. Функция сравнения сборок Develocity позволяет легко сравнивать входные данные задач между двумя сборками и выявлять недетерминированность.
Запустите скрипты проверки сборки самостоятельно. У скриптов проверки сборки Develocity открытый исходный код и они бесплатны для использования. Они точно покажут, какой запас кэширования есть у вашего проекта, и укажут на конкретные задачи, требующие внимания. В качестве еще одного примера такого подхода в масштабе можно посмотреть, как Netflix использует скрипты проверки сборки для мониторинга производительности сборки.
Не стоит недооценивать небольшую экономию времени на выполнение отдельных задач. В проекте DuckDuckGo тысячи задач. Исправление поведения кэша для всех них позволило сэкономить почти 2 часа времени последовательного выполнения за полный цикл сборки. Экономия времени зависит от параллелизма, но экономия затрат на CI прямо пропорциональна времени последовательного выполнения.
Изучите, что еще предлагает Develocity помимо кэширования сборки. В этом посте основное внимание уделялось оптимизации кэширования сборки и оптимизации предотвращения выполнения тестов, но Develocity предоставляет более широкий набор инструментов, заслуживающих внимания. Test Distribution распараллеливает выполнение тестов на нескольких агентах для более быстрой обратной связи по большим наборам тестов. Платформа Universal Cache выходит за рамки кэширования результатов выполнения задач — Artifact Cache действует как слой кэширования на основе локальной сети для зависимостей сборки (Maven Central, npm, внутренние репозитории), обеспечивая как минимум в 6 раз более быстрое разрешение зависимостей на временных агентах CI, а Setup Cache ускоряет инициализацию сборки Gradle, восстанавливая предварительно вычисленное состояние, такое как скомпилированные скрипты сборки и хеши файлов, сокращая время фазы конфигурации примерно на 50%. Dependency Provenance Governor обеспечивает соблюдение политик цепочки поставок, определяющих, какие зависимости разрешены в сборках. И каждая сборка автоматически создает Build Scan с анализом критического пути, мониторингом использования ресурсов и диагностикой сбоев — что упрощает проведение исследований, описанных в этой статье.
Сделайте ваши данные сборки доступными для агентов ИИ. Исследования, лежащие в основе этой статьи — сопоставление промахов кэша на разных машинах, сравнение входных данных задач, поиск недетерминированности в сгенерированном коде — сами по себе были ускорены агентами ИИ, запрашивающими Develocity через его MCP-сервер. Проблему с промахом кэша CMake, в частности, было проще решить, когда агент смог получать входные данные задачи и ключи кэша непосредственно из Develocity, вместо того чтобы мы вставляли фрагменты логов в чат. Сервер MCP предоставляет помощникам-программистам на основе ИИ доступ к результатам сканирования сборки, аналитике тестирования и ошибкам, поэтому агенту доступна та же самая информация, которую проверил бы инженер-человек, и он может рассуждать вместе с ним.
-
Новости3 недели назадВидео и подкасты о мобильной разработке 2026.20
-
Видео и подкасты для разработчиков3 недели назадОт личной продуктивности к командной: сила шаблонизации в IDE
-
Новости4 недели назадВидео и подкасты о мобильной разработке 2026.19
-
Разработка4 недели назадПодсветка синтаксиса на Android — интеграция движка Shiki в Compose
