Отладка базовых профилей

В этом документе представлены рекомендации, которые помогут диагностировать проблемы и обеспечить правильную работу базовых профилей для получения максимальной пользы.

Проблемы со сборкой

Если вы скопировали пример «Базовые профили» в примере приложения «Теперь в Android» , вы можете столкнуться с ошибками тестов во время задачи «Базовый профиль», в которых будет указано, что тесты невозможно запустить на эмуляторе:

./gradlew assembleDemoRelease
Starting a Gradle Daemon (subsequent builds will be faster)
Calculating task graph as no configuration cache is available for tasks: assembleDemoRelease
Type-safe project accessors is an incubating feature.

> Task :benchmarks:pixel6Api33DemoNonMinifiedReleaseAndroidTest
Starting 14 tests on pixel6Api33

com.google.samples.apps.nowinandroid.foryou.ScrollForYouFeedBenchmark > scrollFeedCompilationNone[pixel6Api33] FAILED
        java.lang.AssertionError: ERRORS (not suppressed): EMULATOR
        WARNINGS (suppressed):
        ...

Сбои возникают из-за того, что Now в Android использует устройство, управляемое Gradle, для создания базового профиля. Сбои ожидаемы, поскольку обычно не следует запускать тесты производительности на эмуляторе. Однако, поскольку при создании базовых профилей вы не собираете показатели производительности, для удобства вы можете запускать сбор базовых профилей на эмуляторах. Чтобы использовать базовые профили с эмулятором, выполните сборку и установку из командной строки и задайте аргумент для включения правил базовых профилей:

installDemoRelease -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=BaselineProfile

Кроме того, вы можете создать собственную конфигурацию запуска в Android Studio, чтобы включить базовые профили в эмуляторах, выбрав «Выполнить» > «Редактировать конфигурации» :

Добавьте пользовательскую конфигурацию запуска для создания базовых профилей в Now в Android.
Рис. 1. Добавьте пользовательскую конфигурацию запуска для создания базовых профилей в Now в Android.

Проблемы с установкой

Убедитесь, что создаваемый вами APK или AAB относится к варианту сборки, включающему базовые профили. Самый простой способ проверить это — открыть APK в Android Studio, выбрав «Сборка» > «Анализ APK» , открыв APK и найдя профиль в файле /assets/dexopt/baseline.prof :

Проверьте базовый профиль с помощью APK Viewer в Android Studio
Рисунок 2. Проверьте базовый профиль с помощью APK Viewer в Android Studio.

Базовые профили необходимо скомпилировать на устройстве, на котором работает приложение. Как для установок из магазина приложений, так и для приложений, установленных с помощью PackageInstaller , компиляция на устройстве происходит как часть процесса установки приложения. Однако когда приложение загружается из Android Studio или с помощью инструментов командной строки, библиотека Jetpack ProfileInstaller отвечает за постановку профилей в очередь для компиляции во время следующего процесса фоновой оптимизации DEX. В таких случаях, если вы хотите убедиться, что ваши базовые профили используются, вам может потребоваться принудительно компилировать базовые профили . ProfileVerifier позволяет запрашивать статус установки и компиляции профиля, как показано в следующем примере:

Котлин

private const val TAG = "MainActivity"

class MainActivity : ComponentActivity() {
  ...
  override fun onResume() {
    super.onResume()
    lifecycleScope.launch {
      logCompilationStatus()
    }
  }

  private suspend fun logCompilationStatus() {
     withContext(Dispatchers.IO) {
        val status = ProfileVerifier.getCompilationStatusAsync().await()
        when (status.profileInstallResultCode) {
            RESULT_CODE_NO_PROFILE ->
                Log.d(TAG, "ProfileInstaller: Baseline Profile not found")
            RESULT_CODE_COMPILED_WITH_PROFILE ->
                Log.d(TAG, "ProfileInstaller: Compiled with profile")
            RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION ->
                Log.d(TAG, "ProfileInstaller: Enqueued for compilation")
            RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING ->
                Log.d(TAG, "ProfileInstaller: App was installed through Play store")
            RESULT_CODE_ERROR_PACKAGE_NAME_DOES_NOT_EXIST ->
                Log.d(TAG, "ProfileInstaller: PackageName not found")
            RESULT_CODE_ERROR_CACHE_FILE_EXISTS_BUT_CANNOT_BE_READ ->
                Log.d(TAG, "ProfileInstaller: Cache file exists but cannot be read")
            RESULT_CODE_ERROR_CANT_WRITE_PROFILE_VERIFICATION_RESULT_CACHE_FILE ->
                Log.d(TAG, "ProfileInstaller: Can't write cache file")
            RESULT_CODE_ERROR_UNSUPPORTED_API_VERSION ->
                Log.d(TAG, "ProfileInstaller: Enqueued for compilation")
            else ->
                Log.d(TAG, "ProfileInstaller: Profile not compiled or enqueued")
        }
    }
}

Ява

public class MainActivity extends ComponentActivity {

    private static final String TAG = "MainActivity";

    @Override
    protected void onResume() {
        super.onResume();

        logCompilationStatus();
    }

    private void logCompilationStatus() {
         ListeningExecutorService service = MoreExecutors.listeningDecorator(
                Executors.newSingleThreadExecutor());
        ListenableFutureP<rofileVerifier.CompilationStatus >future =
                ProfileVerifier.getCompilationStatusAsync();
        Futures.addCallback(future, new FutureCallback(<>) {
            @Override
            public void onSuccess(CompilationStatus result) {
                int resultCode = result.getProfileInstallResultCode();
                if (resultCode == RESULT_CODE_NO_PROFILE) {
                    Log.d(TAG, "ProfileInstaller: Baseline Profile not found");
                } else if (resultCode == RESULT_CODE_COMPILED_WITH_PROFILE) {
                    Log.d(TAG, "ProfileInstaller: Compiled with profile");
                } else if (resultCode == RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION) {
                    Log.d(TAG, "ProfileInstaller: Enqueued for compilation");
                } else if (resultCode == RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING) {
                    Log.d(TAG, "ProfileInstaller: App was installed through Play store");
                } else if (resultCode == RESULT_CODE_ERROR_PACKAGE_NAME_DOES_NOT_EXIST) {
                    Log.d(TAG, "ProfileInstaller: PackageName not found");
                } else if (resultCode == RESULT_CODE_ERROR_CACHE_FILE_EXISTS_BUT_CANNOT_BE_READ) {
                    Log.d(TAG, "ProfileInstaller: Cache file exists but cannot be read");
                } else if (resultCode
                        == RESULT_CODE_ERROR_CANT_WRITE_PROFILE_VERIFICATION_RESULT_CACHE_FILE) {
                    Log.d(TAG, "ProfileInstaller: Can't write cache file");
                } else if (resultCode == RESULT_CODE_ERROR_UNSUPPORTED_API_VERSION) {
                    Log.d(TAG, "ProfileInstaller: Enqueued for compilation");
                } else {
                    Log.d(TAG, "ProfileInstaller: Profile not compiled or enqueued");
                }
            }

            @Override
            public void onFailure(Throwable t) {
                Log.d(TAG,
                        "ProfileInstaller: Error getting installation status: " + t.getMessage());
            }
        }, service);
    }
}

Следующие коды результатов подсказывают причины некоторых проблем:

RESULT_CODE_COMPILED_WITH_PROFILE
Профиль устанавливается, компилируется и используется при каждом запуске приложения. Это результат, который вы хотите увидеть.
RESULT_CODE_ERROR_NO_PROFILE_EMBEDDED
В запущенном APK или AAB профиль не найден. Если вы видите эту ошибку, убедитесь, что вы используете вариант сборки, включающий базовые профили, и что APK содержит профиль.
RESULT_CODE_NO_PROFILE
При установке приложения через магазин приложений или диспетчер пакетов для этого приложения не был установлен профиль. Основная причина появления этого кода ошибки заключается в том, что установщик профиля не запустился из-за отключения ProfileInstallerInitializer . Обратите внимание: когда сообщается об этой ошибке, в APK-файле приложения все еще обнаруживается встроенный профиль. Если встроенный профиль не найден, возвращается код ошибки: RESULT_CODE_ERROR_NO_PROFILE_EMBEDDED .
RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION
Профиль находится в APK или AAB и ставится в очередь на компиляцию. Когда профиль устанавливается с помощью ProfileInstaller , он ставится в очередь для компиляции при следующем запуске системой фоновой оптимизации DEX. Профиль неактивен до завершения компиляции. Не пытайтесь протестировать базовые профили до завершения компиляции. Возможно, вам придется принудительно компилировать базовые профили . Эта ошибка не возникнет, если приложение установлено из магазина приложений или диспетчера пакетов на устройствах под управлением Android 9 (API 28) и выше, поскольку компиляция выполняется во время установки.
RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING
Установлен несоответствующий профиль, и приложение скомпилировано с ним. Это результат установки через магазин Google Play или менеджер пакетов. Обратите внимание, что этот результат отличается от RESULT_CODE_COMPILED_WITH_PROFILE , поскольку несовпадающий профиль будет компилировать только те методы, которые по-прежнему используются профилем и приложением. Фактически профиль меньше ожидаемого, и будет скомпилировано меньше методов, чем было включено в базовый профиль.
RESULT_CODE_ERROR_CANT_WRITE_PROFILE_VERIFICATION_RESULT_CACHE_FILE
ProfileVerifier не может записать файл кэша результатов проверки. Это может произойти либо из-за того, что что-то не так с разрешениями для папки приложения, либо из-за того, что на устройстве недостаточно свободного места на диске.
RESULT_CODE_ERROR_UNSUPPORTED_API_VERSION
ProfileVerifier is running on an unsupported API version of Android. ProfileVerifier поддерживает только Android 9 (уровень API 28) и выше.
RESULT_CODE_ERROR_PACKAGE_NAME_DOES_NOT_EXIST
PackageManager.NameNotFoundException генерируется при запросе PackageManager для пакета приложения. Это должно происходить редко. Попробуйте удалить приложение и установить все заново.
RESULT_CODE_ERROR_CACHE_FILE_EXISTS_BUT_CANNOT_BE_READ
Файл кэша результатов предыдущей проверки существует, но его невозможно прочитать. Это должно происходить редко. Попробуйте удалить приложение и установить все заново.

Используйте ProfileVerifier в производстве

В рабочей среде вы можете использовать ProfileVerifier в сочетании с библиотеками аналитических отчетов, такими как Google Analytics для Firebase , для создания аналитических событий, указывающих состояние профиля. Например, это быстро оповестит вас, если выйдет новая версия приложения, не содержащая базовые профили.

Принудительная компиляция базовых профилей

Если статус компиляции ваших базовых профилей — RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION , вы можете принудительно выполнить немедленную компиляцию с помощью adb :

adb shell cmd package compile -r bg-dexopt PACKAGE_NAME

Проверьте состояние компиляции без ProfileVerifier

Если вы не используете ProfileVerifier , вы можете проверить состояние компиляции с помощью adb , хотя он не дает такой глубокой информации, как ProfileVerifier :

adb shell dumpsys package dexopt | grep -A 2 PACKAGE_NAME

Использование adb дает что-то похожее на следующее:

  [com.google.samples.apps.nowinandroid.demo]
    path: /data/app/~~dzJiGMKvp22vi2SsvfjkrQ==/com.google.samples.apps.nowinandroid.demo-7FR1sdJ8ZTy7eCLwAnn0Vg==/base.apk
      arm64: [status=speed-profile] [reason=bg-dexopt] [primary-abi]
        [location is /data/app/~~dzJiGMKvp22vi2SsvfjkrQ==/com.google.samples.apps.nowinandroid.demo-7FR1sdJ8ZTy7eCLwAnn0Vg==/oat/arm64/base.odex]

Значение статуса указывает на статус компиляции профиля и может принимать одно из следующих значений:

Статус компиляции Значение
speed‑profile Скомпилированный профиль существует и используется.
verify Скомпилированного профиля не существует.

Статус verify не означает, что APK или AAB не содержат профиля, поскольку он может быть поставлен в очередь для компиляции следующей фоновой задачей оптимизации DEX.

Значение причины указывает, что вызывает компиляцию профиля, и может принимать одно из следующих значений:

Причина Значение
install‑dm Базовый профиль был составлен вручную или с помощью Google Play при установке приложения.
bg‑dexopt Профиль был составлен, пока ваше устройство простаивало. Это может быть базовый профиль или профиль, собранный во время использования приложения.
cmdline Компиляция запускалась с помощью adb. Это может быть базовый профиль или профиль, собранный во время использования приложения.

Проблемы с производительностью

В этом разделе показаны некоторые рекомендации по правильному определению и сравнительному анализу базовых профилей, чтобы получить от них максимальную выгоду.

Правильно сравнивайте метрики запуска

Ваши базовые профили будут более эффективными, если показатели вашего запуска будут четко определены. Двумя ключевыми показателями являются время начального отображения (TTID) и время полного отображения (TTFD) .

TTID — это момент, когда приложение рисует свой первый кадр. Важно сделать это как можно короче, потому что отображение чего-либо показывает пользователю, что приложение запущено. Вы даже можете отобразить неопределенный индикатор прогресса, чтобы показать, что приложение реагирует.

TTFD — это когда с приложением действительно можно взаимодействовать. Важно сделать это как можно короче, чтобы избежать разочарования пользователей. Если вы правильно сигнализируете TTFD, вы сообщаете системе, что код, который запускается на пути к TTFD, является частью запуска приложения. В результате система с большей вероятностью разместит этот код в профиле.

Держите значения TTID и TTFD как можно ниже, чтобы ваше приложение было отзывчивым.

Система способна обнаруживать TTID, отображать его в Logcat и сообщать о нем как часть тестов при запуске. Однако система не может определить TTFD, и приложение обязано сообщить, когда оно достигнет полностью нарисованного интерактивного состояния. Вы можете сделать это, вызвав reportFullyDrawn() или ReportDrawn , если вы используете Jetpack Compose. Если у вас есть несколько фоновых задач, которые необходимо выполнить, прежде чем приложение будет считаться полностью прорисованным, вы можете использовать FullyDrawnReporter , как описано в разделе «Повышение точности времени запуска» .

Профили библиотеки и пользовательские профили

При оценке влияния профилей может быть сложно отделить преимущества профилей вашего приложения от профилей, предоставляемых библиотеками, такими как библиотеки Jetpack. Когда вы создаете APK, плагин Android Gradle добавляет любые профили в зависимостях библиотеки, а также ваш собственный профиль. Это полезно для оптимизации общей производительности и рекомендуется для ваших выпускных сборок. Однако сложно измерить, какой дополнительный прирост производительности дает ваш собственный профиль.

Быстрый способ вручную увидеть дополнительную оптимизацию, предоставляемую вашим пользовательским профилем, — это удалить ее и запустить тесты. Затем замените его и снова запустите тесты. Сравнение этих двух показателей покажет вам оптимизацию, обеспечиваемую только профилями библиотеки, а также профилями библиотеки плюс ваш собственный профиль.

Автоматизированный способ сравнения профилей — создание нового варианта сборки, содержащего только профили библиотеки, а не ваш собственный профиль. Сравните тесты этого варианта с вариантом выпуска, который содержит как профили библиотеки, так и ваши пользовательские профили. В следующем примере показано, как настроить вариант, включающий только профили библиотеки. Добавьте новый вариант с именем releaseWithoutCustomProfile в потребительский модуль вашего профиля, который обычно является модулем вашего приложения:

Котлин

android {
  ...
  buildTypes {
    ...
    // Release build with only library profiles.
    create("releaseWithoutCustomProfile") {
      initWith(release)
    }
    ...
  }
  ...
}
...
dependencies {
  ...
  // Remove the baselineProfile dependency.
  // baselineProfile(project(":baselineprofile"))
}

baselineProfile {
  variants {
    create("release") {
      from(project(":baselineprofile"))
    }
  }
}

классный

android {
  ...
  buildTypes {
    ...
    // Release build with only library profiles.
    releaseWithoutCustomProfile {
      initWith(release)
    }
    ...
  }
  ...
}
...
dependencies {
  ...
  // Remove the baselineProfile dependency.
  // baselineProfile ':baselineprofile"'
}

baselineProfile {
  variants {
    release {
      from(project(":baselineprofile"))
    }
  }
}

В предыдущем примере кода зависимость baselineProfile удаляется из всех вариантов и выборочно применяется только к варианту release . Может показаться нелогичным, что профили библиотеки продолжают добавляться даже после удаления зависимости от модуля производителя профиля. Однако этот модуль отвечает только за создание вашего пользовательского профиля. Плагин Android Gradle по-прежнему работает для всех вариантов и отвечает за включение профилей библиотеки.

Вам также необходимо добавить новый вариант в модуль генератора профилей. В этом примере модуль-производитель называется :baselineprofile .

Котлин

android {
  ...
    buildTypes {
      ...
      // Release build with only library profiles.
      create("releaseWithoutCustomProfile") {}
      ...
    }
  ...
}

классный

android {
  ...
    buildTypes {
      ...
      // Release build with only library profiles.
      releaseWithoutCustomProfile {}
      ...
    }
  ...
}

При запуске теста из Android Studio выберите вариант releaseWithoutCustomProfile , чтобы измерять производительность только с профилями библиотеки, или выберите вариант release , чтобы измерять производительность с помощью библиотеки и пользовательских профилей.

Избегайте запуска приложений, связанных с вводом-выводом

Если ваше приложение выполняет много вызовов ввода-вывода или сетевых вызовов во время запуска, это может отрицательно повлиять как на время запуска приложения, так и на точность сравнительного анализа запуска. Эти тяжелые вызовы могут занимать неопределенное количество времени, которое может меняться со временем и даже между итерациями одного и того же теста. Вызовы ввода-вывода обычно лучше сетевых вызовов, поскольку на последние могут влиять факторы, внешние по отношению к устройству и на самом устройстве. Избегайте сетевых вызовов во время запуска. Если использование того или иного метода неизбежно, используйте ввод-вывод.

Мы рекомендуем сделать так, чтобы архитектура вашего приложения поддерживала запуск приложения без сетевых вызовов или вызовов ввода-вывода, хотя бы для использования только при тестировании запуска. Это помогает обеспечить минимально возможную вариативность между различными итерациями ваших тестов.

Если ваше приложение использует Hilt, вы можете предоставить поддельные реализации с привязкой к вводу-выводу при тестировании в Microbenchmark и Hilt .

Охватите все важные пути пользователя

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