Depurar perfis de referência

Este documento fornece práticas recomendadas e etapas de solução de problemas para ajudar a diagnosticar problemas e garantir que seus perfis de referência funcionem corretamente para oferecer o máximo de benefícios.

Problemas de build

Se você copiou o exemplo de perfis de referência no app de exemplo Now in Android (link em inglês), é possível que encontre falhas no teste durante a tarefa do perfil de referência, afirmando que os testes não podem ser executados em um emulador:

./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):
        ...

As falhas ocorrem porque o Now in Android usa um dispositivo gerenciado pelo Gradle para gerar perfis de referência. As falhas são esperadas, porque geralmente não é recomendável executar comparações de desempenho em um emulador. No entanto, como você não está coletando métricas de desempenho ao gerar perfis de referência, é possível coletar esses perfis em emuladores por conveniência. Para usar perfis de referência com um emulador, execute o build e a instalação na linha de comando e defina um argumento para ativar as regras de perfis de referência:

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

Como alternativa, você pode criar uma configuração de execução personalizada no Android Studio para ativar perfis de referência em emuladores, selecionando Run > Edit Configurations:

Adicionar uma configuração de execução personalizada para criar perfis de referência no Now in Android
Figura 1. Adicione uma configuração de execução personalizada para criar perfis de referência no Now in Android.

Verificar a instalação e a aplicação do perfil

Para verificar se o APK ou Android App Bundle (AAB) que você está inspecionando é de uma variante de build que inclui perfis de referência, faça o seguinte:

  1. No Android Studio, selecione Build > Analyze APK.
  2. Abra o AAB ou APK.
  3. Confirme se o arquivo baseline.prof existe:

    • Se você estiver inspecionando um AAB, o perfil estará em /BUNDLE-METADATA/com.android.tools.build.profiles/baseline.prof.
    • Se você estiver inspecionando um APK, o perfil estará em /assets/dexopt/baseline.prof.

      A presença desse arquivo é o primeiro sinal de uma configuração de build correta. Se ele estiver faltando, significa que o Android Runtime não vai receber instruções de pré-compilação no momento da instalação.

      Verificar se há um perfil de referência usando o APK Analyzer no Android Studio
      Figura 2. Verifique se há um perfil de referência usando o APK Analyzer no Android Studio.

Os perfis de referência precisam ser compilados no dispositivo que executa o app. Quando você instala builds não depuráveis usando o Android Studio ou a ferramenta de linha de comando do wrapper do Gradle, a compilação no dispositivo acontece automaticamente. Se você instalar o app na Google Play Store, os perfis de linha de base serão compilados durante atualizações em segundo plano do dispositivo, e não no momento da instalação. Quando o app é instalado usando outras ferramentas, a biblioteca ProfileInstaller do Jetpack é responsável por enfileirar os perfis para compilação durante o próximo processo de otimização de DEX em segundo plano.

Nesses casos, se você quiser garantir que os perfis de referência estejam sendo usados, talvez seja necessário forçar a compilação de perfis de referência. O ProfileVerifier permite consultar o status da instalação e compilação do perfil, conforme mostrado no exemplo abaixo:

Kotlin

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

Java

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());
        ListenableFuture<ProfileVerifier.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);
    }
}

Os códigos de resultado abaixo dão dicas sobre a causa de alguns problemas:

RESULT_CODE_COMPILED_WITH_PROFILE
O perfil foi instalado e compilado, e será usado sempre que o app for executado. Esse é o resultado que você quer encontrar.
RESULT_CODE_ERROR_NO_PROFILE_EMBEDDED
Nenhum perfil foi encontrado no APK em execução. Verifique se você está usando uma variante de build que inclua perfis de referência caso esse erro apareça e se o APK contém um perfil.
RESULT_CODE_NO_PROFILE
Nenhum perfil foi instalado para este app durante a instalação pela app store ou pelo gerenciador de pacotes. O principal motivo para o código do erro é que o instalador do perfil não foi executado, porque o ProfileInstallerInitializer estava desativado. Quando esse erro é informado, um perfil incorporado ainda é encontrado no APK do aplicativo. Quando um perfil incorporado não é encontrado, o código do erro retornado é RESULT_CODE_ERROR_NO_PROFILE_EMBEDDED.
RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION
Um perfil foi encontrado no APK ou AAB e foi colocado na fila para compilação. Quando um perfil é instalado pelo ProfileInstaller, ele é colocado na fila para compilação na próxima vez que a otimização DEX em segundo plano for executada pelo sistema. O perfil não fica ativo até que a compilação seja concluída. Não tente comparar seus perfis de referência até que a compilação seja concluída. Talvez seja necessário forçar a compilação de perfis de referência. Esse erro não vai ocorrer quando o app for instalado pela Play Store ou pelo gerenciador de pacotes em dispositivos com o Android 9 (API 28) ou versões mais recentes, porque a compilação é realizada durante a instalação.
RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING
Um perfil não correspondente foi instalado e o app foi compilado com ele. Isso acontece porque a instalação foi feita pela Google Play Store ou pelo gerenciador de pacotes. Esse resultado é diferente de RESULT_CODE_COMPILED_WITH_PROFILE, porque o perfil não correspondente compila apenas os métodos que ainda estão compartilhados entre o perfil e o app. O perfil é efetivamente menor do que o esperado e menos métodos serão compilados do que os incluídos no perfil de referência.
RESULT_CODE_ERROR_CANT_WRITE_PROFILE_VERIFICATION_RESULT_CACHE_FILE
ProfileVerifier não pode gravar o arquivo de cache de resultados da verificação. Isso pode acontecer porque há algo errado com as permissões da pasta do app ou porque não há espaço livre em disco suficiente no dispositivo.
RESULT_CODE_ERROR_UNSUPPORTED_API_VERSION
O ProfileVerifieris running on an unsupported API version of Android. ProfileVerifier oferece suporte apenas ao Android 9 (nível 28 da API) e versões mais recentes.
RESULT_CODE_ERROR_PACKAGE_NAME_DOES_NOT_EXIST
Uma PackageManager.NameNotFoundException é gerada ao consultar o PackageManager do pacote do app. Isso raramente deveria acontecer. Tente desinstalar o app e reinstalar tudo.
RESULT_CODE_ERROR_CACHE_FILE_EXISTS_BUT_CANNOT_BE_READ
Há um arquivo de cache de resultados da verificação anterior, mas ele não pode ser lido. Isso raramente deveria acontecer. Tente desinstalar o app e reinstalar tudo.

Usar ProfileVerifier em produção

Na produção, você pode usar o ProfileVerifier com bibliotecas de relatórios de análise, como o Google Analytics para Firebase, para gerar eventos de análise que indicam o status do perfil. Por exemplo, isso vai alertar você rapidamente se for lançada uma nova versão do app que não contenha perfis de referência.

Forçar a compilação de perfis de referência

Se o status de compilação dos perfis de referência for RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION, você poderá forçar a compilação imediata usando adb:

adb shell cmd package compile -r bg-dexopt PACKAGE_NAME

Verificar o estado da compilação do perfil de referência sem ProfileVerifier

Caso não esteja usando o ProfileVerifier, é possível verificar o estado de compilação usando o adb, embora ele não forneça insights tão profundos quanto o ProfileVerifier:

adb shell dumpsys package dexopt | grep -A 2 PACKAGE_NAME

O uso do adb produz algo semelhante a:

  [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]

O valor indica o status de compilação do perfil e é um dos valores abaixo:

Status da compilação Significado
speed‑profile Um perfil compilado existe e está sendo usado.
verify Não existe um perfil compilado.

Um status verify não significa que o APK ou o AAB não contém um perfil, porque ele pode ser colocado na fila para compilação pela próxima tarefa de otimização de DEX em segundo plano.

O motivo do valor indica o que aciona a compilação do perfil e é um destes valores:

Motivo Significado
install‑dm Um perfil de referência foi compilado manualmente ou pelo Google Play quando o app foi instalado.
bg‑dexopt Um perfil foi compilado enquanto seu dispositivo estava inativo. Pode ser um perfil de referência ou um perfil coletado durante o uso do app.
cmdline A compilação foi acionada usando o adb. Pode ser um perfil de referência ou um perfil coletado durante o uso do app.

Verificar a aplicação do perfil de inicialização no DEX e no r8.json

As regras de perfil de inicialização são usadas no momento da build pelo R8 para otimizar o layout das classes nos arquivos DEX. Essa otimização no momento da build é diferente de como os perfis de referência (baseline.prof) são usados, já que eles são empacotados no APK ou AAB para que o ART execute a compilação no dispositivo. Como as regras de perfil de inicialização são aplicadas durante o processo de build, não há um arquivo startup.prof separado no APK ou AAB para inspeção. O efeito dos perfis de inicialização é visível no layout do arquivo DEX.

Inspecionar a organização DEX com r8.json (recomendado para AGP 8.8 ou mais recente)

Para projetos que usam o plug-in do Android para Gradle (AGP) 8.8 ou mais recente, é possível verificar se o perfil de inicialização foi aplicado inspecionando o arquivo r8.json gerado. Esse arquivo é empacotado no seu AAB.

  1. Abra o arquivo AAB e localize o arquivo r8.json.
  2. Pesquise no arquivo a matriz dexFiles, que lista os arquivos DEX gerados.
  3. Procure um objeto dexFiles que contenha o par de chave-valor "startup": true. Isso indica explicitamente que as regras do perfil de inicialização foram aplicadas para otimizar o layout desse arquivo DEX específico.

    "dexFiles": [
     {
       "checksum": "...",
       "startup": true // This flag confirms profile application to this DEX file
     },
     // ... other DEX files
    ]
    

Inspecionar a organização DEX para todas as versões do AGP

Se você estiver usando uma versão do AGP anterior à 8.8, inspecionar os arquivos DEX será a principal maneira de verificar se o perfil de inicialização foi aplicado corretamente. Você também pode usar esse método se estiver usando o AGP 8.8 ou mais recente e quiser verificar manualmente o layout DEX. Por exemplo, se você não estiver percebendo as melhorias de performance esperadas. Para inspecionar a organização DEX, faça o seguinte:

  1. Abra o AAB ou APK usando Build > Analyze APK no Android Studio.
  2. Navegue até o primeiro arquivo DEX. Por exemplo, classes.dex.
  3. Inspecione o conteúdo desse arquivo DEX. Você poderá verificar se as classes e métodos críticos definidos no arquivo de perfil de inicialização (startup-prof.txt) estão presentes no arquivo DEX principal. Uma inscrição bem-sucedida significa que esses componentes essenciais para startups são priorizados para carregamento mais rápido.

Problemas de desempenho

Esta seção mostra algumas práticas recomendadas para definir e comparar corretamente seus perfis de referência e aproveitar ao máximo os benefícios deles.

Comparar corretamente as métricas de inicialização

Os perfis de referência serão mais eficazes se as métricas de inicialização forem bem definidas. As duas métricas principais são tempo para exibição inicial (TTID, na sigla em inglês) e tempo para exibição total (TTFD, na sigla em inglês).

O TTID representa quando o app renderiza o primeiro frame. É importante que ele seja o mais curto possível, porque quando algo aparece na tela, o usuário sabe que o app está em execução. Você pode até mesmo mostrar um indicador de progresso indeterminado para mostrar que o app é responsivo.

O TTFD ocorre quando é possível interagir com o app. É importante que ele seja o mais breve possível para evitar a frustração do usuário. Se você sinalizar corretamente o TTFD, vai informar ao sistema que o código executado no caminho para o TTFD faz parte da inicialização do app. É mais provável que o sistema coloque esse código no perfil como um resultado.

Mantenha o TTID e o TTFD o mais curtos possíveis para tornar o app responsivo.

O sistema consegue detectar o TTID, mostrá-lo no Logcat e informar como parte das comparações de inicialização. No entanto, o sistema não consegue determinar o TTFD, e é responsabilidade do app informar quando atinge um estado interativo totalmente renderizado. Para fazer isso, chame reportFullyDrawn() ou ReportDrawn, se você estiver usando o Jetpack Compose. Se você tiver várias tarefas em segundo plano que precisam ser concluídas antes que o app seja considerado totalmente renderizado, use FullyDrawnReporter, conforme descrito em Melhorar a precisão da marcação do tempo de inicialização.

Perfis de biblioteca e perfis personalizados

Ao comparar o impacto dos perfis, pode ser difícil separar os benefícios dos perfis do seu app daqueles contribuídos por bibliotecas, como as do Jetpack. Ao criar o APK, o plug-in do Android para Gradle adiciona todos os perfis nas dependências de biblioteca, além do seu perfil personalizado. Isso é bom para otimizar o desempenho geral e é recomendado para builds de lançamento. No entanto, isso dificulta a medição de quanto ganho de performance adicional vem do seu perfil personalizado.

Uma maneira rápida de verificar manualmente a otimização adicional fornecida pelo seu perfil personalizado é remover e executar os comparativos de mercado. Em seguida, substitua e execute os comparativos de mercado novamente. Ao comparar os dois, você vai ver as otimizações fornecidas apenas pelos perfis de biblioteca e pelos perfis de biblioteca mais seu perfil personalizado.

Uma maneira automatizada de comparar perfis é criar uma nova variante de build que contenha apenas os perfis de biblioteca e não o seu perfil personalizado. Compare os comparativos de mercado dessa variante com a variante de lançamento que contém os perfis da biblioteca e os personalizados. O exemplo a seguir mostra como configurar a variante que inclui apenas perfis de biblioteca. Adicione uma nova variante chamada releaseWithoutCustomProfile ao módulo consumidor de perfil, que geralmente é o módulo do app:

Kotlin

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

Groovy

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

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

O exemplo de código anterior remove a dependência baselineProfile de todas as variantes e a aplica seletivamente apenas à variante release. Pode parecer contraditório que os perfis de biblioteca ainda sejam adicionados quando a dependência do módulo produtor de perfil é removida. No entanto, esse módulo é responsável apenas por gerar seu perfil personalizado. O plug-in do Android para Gradle ainda está em execução para todas as variantes e é responsável por incluir perfis de biblioteca.

Você também precisa adicionar a nova variante ao módulo gerador de perfil. Neste exemplo, o módulo produtor é chamado de :baselineprofile.

Kotlin

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

Groovy

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

Ao executar o comparativo de performance no Android Studio, selecione uma variante releaseWithoutCustomProfile para medir o desempenho apenas com perfis de biblioteca ou uma variante release para medir o desempenho com perfis de biblioteca e personalizados.

Evitar a inicialização do app vinculada à E/S

Se o app estiver realizando muitas chamadas de E/S ou de redes durante a inicialização, isso poderá afetar negativamente o tempo de inicialização do app e a precisão da comparação de inicialização. Essas chamadas pesadas podem levar um tempo indeterminado, que pode variar até mesmo entre iterações da mesma comparação. As chamadas de E/S geralmente são melhores que as chamadas de rede, porque as últimas podem ser afetadas por fatores externos ao dispositivo e no próprio dispositivo. Evite chamadas de rede durante a inicialização. Quando o uso de um ou outro tipo de chamada for inevitável, use as chamadas de E/S.

Recomendamos que a arquitetura do app ofereça suporte à inicialização do app sem chamadas de rede ou de E/S, mesmo que seja apenas para uso durante a comparação da inicialização. Isso ajuda a garantir a menor variabilidade possível entre diferentes iterações das comparações.

Se o app usa o Hilt, é possível fornecer implementações falsas vinculadas a E/S ao fazer comparativos de mercado em Microbenchmark e Hilt.

Abordar todas as jornadas importantes do usuário

É importante abordar com cuidado todas as jornadas importantes do usuário na geração de perfis de referência. As jornadas do usuário que não forem cobertas não serão melhoradas pelos perfis de referência. Os perfis de referência mais eficazes incluem todas as jornadas comuns do usuário na inicialização, além das jornadas do usuário no app que exigem maior desempenho, como listas de rolagem.

Mudanças no perfil de tempo de compilação do teste A/B

Como os perfis de inicialização e de referência são uma otimização de tempo de compilação, geralmente não há suporte para testes A/B diretos de diferentes APKs usando a Google Play Store em versões de produção. Para avaliar o impacto em um ambiente semelhante ao de produção, considere as seguintes abordagens:

  • Lançamento fora do ciclo: faça upload de um lançamento fora do ciclo para uma pequena porcentagem da sua base de usuários que inclui apenas a mudança de perfil. Isso permite coletar métricas do mundo real sobre a diferença de performance.

  • Comparativo de mercado local: compare seu app localmente com e sem o perfil aplicado. No entanto, o comparativo local mostra o melhor cenário possível para os perfis, já que não inclui os efeitos dos perfis da nuvem do ART presentes em dispositivos de produção.