Perfis de referência

Os perfis de referência são uma lista de classes e métodos incluídos em um APK usado pelo Android Runtime (ART) durante a instalação para pré-compilar caminhos essenciais para o código de máquina. Essa é uma forma de otimização guiada por perfil (PGO, na sigla em inglês) que permite aos apps otimizar a inicialização, reduzir a instabilidade e melhorar a performance para os usuários finais.

Como funcionam os perfis de referência

As regras de perfil são compiladas em um formato binário no APK, em assets/dexopt/baseline.prof.

Durante a instalação, o ART realiza a compilação antecipada (AOT, na sigla em inglês) de métodos no perfil, resultando em uma execução mais rápida deles. Se o perfil tiver métodos usados na inicialização do app ou durante a renderização do frame, o tempo para a inicialização vai ser mais rápido e a instabilidade do app será reduzida.

Ao desenvolver seu app ou biblioteca, defina perfis de referência para cobrir caminhos específicos durante jornadas ideais do usuário em que a latência ou o tempo de renderização são importantes, como inicialização, transições ou rolagem.

Os perfis de referência vão ser enviados diretamente aos usuários (pelo Google Play) junto ao APK, como mostrado na Figura 1:

Figura 1. Esse diagrama mostra o fluxo de trabalho do perfil de referência desde o upload até a entrega ao usuário final e como esse fluxo de trabalho se relaciona com os perfis da nuvem.

Motivos para usar perfis de referência

O tempo de inicialização é um componente essencial para melhorar o engajamento do usuário com seu aplicativo. O aumento da velocidade e capacidade de resposta de um app leva a mais usuários ativos por dia e uma taxa média de visitas mais alta.

Os perfis na nuvem também otimizam essas interações, mas ficam disponíveis para os usuários somente um dia ou mais após o lançamento de uma atualização, além de não terem suporte do Android 7 (API 24) ao Android 8 (API 26).

Comportamento de compilação nas versões do Android

As versões da Plataforma Android usaram diferentes abordagens de compilação de apps, cada uma com vantagens específicas. Os perfis de referência melhoram os métodos de compilação anteriores fornecendo um perfil para todas as instalações.

Versão do Android Método de compilação Abordagem de otimização
Android 5 (nível 21 da API) ao Android 6 (nível 23 da API) Antecipação completa O app todo é otimizado durante a instalação, resultando em longos tempos de espera para o uso, aumento no uso de RAM e espaço em disco e tempos mais longos de carregamento do código do disco, o que pode aumentar os tempos de inicialização a frio.
Android 7 (nível 24 da API) ao Android 8.1 (nível 27 da API) Antecipação parcial (perfil de referência) Os perfis de referência são instalados por androidx.profileinstaller na primeira execução, quando o módulo do app define essa dependência. O ART pode melhorar ainda mais o processo adicionando outras regras de perfil durante o uso do app e as compilando quando o dispositivo estiver inativo. Isso otimiza o espaço em disco e o tempo para carregar o código do disco, reduzindo o tempo de espera do app.
Android 9 (nível 28 da API) e versões mais recentes Antecipação parcial (perfil de referência + da nuvem) O Play usa perfis de referência durante a instalação do app para otimizar os perfis de APK e da nuvem (se disponíveis). Após a instalação, é feito o upload dos perfis do ART para o Play e eles são agregados e depois fornecidos como perfis de nuvem para outros usuários na instalação/atualização do app.

Criar perfis de referência

Criar regras de perfil automaticamente usando BaselineProfileRule

Como desenvolvedor de apps, você pode gerar perfis automaticamente para cada lançamento usando a biblioteca Macrobenchmark do Jetpack.

Para criar perfis de referência usando a biblioteca Macrobenchmark, siga estas etapas:

  1. Adicione uma dependência à biblioteca ProfileInstaller no arquivo build.gradle do seu app para ativar a compilação de perfis de referência locais e da Play Store. Essa é a única maneira de transferir por sideload um perfil de referência localmente.

    dependencies {
         implementation("androidx.profileinstaller:profileinstaller:1.2.0-beta01")
    }
    
  2. Configure um módulo da Macrobenchmark no seu projeto do Gradle.

  3. Defina um novo teste com o nome BaselineProfileGenerator parecido com este:

    @ExperimentalBaselineProfilesApi
    @RunWith(AndroidJUnit4::class)
    class BaselineProfileGenerator {
        @get:Rule val baselineProfileRule = BaselineProfileRule()
    
        @Test
        fun startup() =
            baselineProfileRule.collectBaselineProfile(packageName = "com.example.app") {
                pressHome()
                // This block defines the app's critical user journey. Here we are interested in
                // optimizing for app startup. But you can also navigate and scroll
                // through your most important UI.
                startActivityAndWait()
            }
    }
    
  4. Conecte um userdebug ou emulador do Android Open Source Project (AOSP) com acesso root usando Android 9 ou uma versão mais recente.

  5. Execute o comando adb root no terminal para garantir que o daemon do adb esteja em execução com permissões de acesso root.

  6. Execute o teste e aguarde a conclusão.

  7. Encontre o local do perfil gerado no logcat. Pesquise a tag de registro Benchmark.

    com.example.app D/Benchmark: Usable output directory: /storage/emulated/0/Android/media/com.example.app
    
    # List the output baseline profile
    ls /storage/emulated/0/Android/media/com.example.app
    SampleStartupBenchmark_startup-baseline-prof.txt
    
  8. Extraia o arquivo gerado do dispositivo.

    adb pull storage/emulated/0/Android/media/com.example.app/SampleStartupBenchmark_startup-baseline-prof.txt .
    
  9. Renomeie o arquivo gerado como baseline-prof.txt e o copie para o diretório src/main do módulo do app.

Definir regras de perfil manualmente

Para definir as regras de perfil manualmente em um app ou módulo de biblioteca, crie um arquivo chamado baseline-prof.txt no diretório src/main. Essa é a mesma pasta que contém o arquivo AndroidManifest.xml.

O arquivo especifica uma regra por linha. Cada regra representa um padrão de métodos ou classes de correspondência que precisa ser otimizado no app ou na biblioteca.

A sintaxe dessas regras é um superconjunto do formato de perfil do ART legível por humanos (HRF, na sigla em inglês) ao usar adb shell profman --dump-classes-and-methods. Ela é muito semelhante à sintaxe para descritores e assinaturas, mas também permite o uso de caracteres curinga para simplificar o processo de criação de regras.

Os exemplos abaixo mostram algumas regras de perfil de referência incluídas na biblioteca do Jetpack Compose:

HSPLandroidx/compose/runtime/ComposerImpl;->updateValue(Ljava/lang/Object;)V
HSPLandroidx/compose/runtime/ComposerImpl;->updatedNodeCount(I)I
HLandroidx/compose/runtime/ComposerImpl;->validateNodeExpected()V
PLandroidx/compose/runtime/CompositionImpl;->applyChanges()V
HLandroidx/compose/runtime/ComposerKt;->findLocation(Ljava/util/List;I)I
Landroidx/compose/runtime/ComposerImpl;

Sintaxe de regras

Essas regras podem ter uma de duas formas destinadas a métodos ou classes:

[FLAGS][CLASS_DESCRIPTOR]->[METHOD_SIGNATURE]

Uma regra de classe usa o seguinte padrão:

[CLASS_DESCRIPTOR]
Sintaxe Descrição
FLAGS Representa um ou mais dos caracteres H, S e P para indicar se esse método será sinalizado como Hot, Startup ou Post Startup em relação ao tipo de inicialização.

Um método com a sinalização H indica que ele é "quente", ou seja, chamado várias vezes durante a vida útil do app.

Um método com a sinalização S indica que ele é chamado na inicialização.

Um método com a sinalização P indica que ele é quente e não tem relação com a inicialização.

Uma classe presente nesse arquivo indica que ela é usada durante a inicialização e precisa ser pré-alocada no heap para evitar o custo do carregamento. O compilador do ART emprega várias estratégias de otimização, como a compilação antecipada desses métodos e otimizações de layout no arquivo gerado antecipadamente.
CLASS_DESCRIPTOR Descritor da classe do método. Por exemplo, androidx.compose.runtime.SlotTable teria um descritor de Landroidx/compose/runtime/SlotTable;. Observação: aqui, L é anexado ao formato Dalvik Executable (DEX).
METHOD_SIGNATURE Assinatura do método, incluindo o nome, os tipos de parâmetro e os tipos de retorno. Por exemplo, o método

// LayoutNode.kt

fun isPlaced():Boolean {
// ...
}

em LayoutNode tem a assinatura isPlaced()Z.

Esses padrões podem ter caracteres curinga para que uma única regra inclua vários métodos ou classes. Para receber assistência guiada ao programar usando a sintaxe de regra no Android Studio, consulte o plug-in Android Baseline Profiles (link em inglês).

Este é um exemplo de regra de caractere curinga:

HSPLandroidx/compose/ui/layout/**->**(**)**

Tipos com suporte nas regras de perfil de referência

As regras de perfil de referência oferecem suporte aos seguintes tipos: Para conferir detalhes sobre esses tipos, consulte o formato Dalvik Executable (DEX).

Caractere Tipo Descrição
B byte Byte assinado
C char Ponto de código de caractere Unicode codificado em UTF-16
D double Valor de ponto flutuante de precisão dupla
F float Valor de ponto flutuante de precisão única
I int Número inteiro
J long Número inteiro longo
S short Número inteiro curto assinado
V void Vazio
Z boolean Verdadeiro ou falso
L (nome da classe) reference Uma instância de um nome de classe

Além disso, as bibliotecas podem definir regras que são empacotadas em artefatos do AAR. Ao criar um APK para incluir esses artefatos, as regras são mescladas (de maneira semelhante à mesclagem de manifestos) e compiladas em um perfil ART binário compacto específico para o APK.

O ART aproveita esse perfil quando o APK é usado em dispositivos para compilar antecipadamente um subconjunto específico do aplicativo durante a instalação no Android 9 (nível 28 da API) ou Android 7 (nível 24 da API) com o ProfileInstaller.

Outras observações

Ao criar perfis de referência, considere alguns outros detalhes:

  • As versões do Android 5 ao Android 6 (níveis 21 a 23 da API) já compilam o APK antecipadamente no momento da instalação.

  • Os aplicativos depuráveis nunca são compilados antecipadamente para ajudar na solução de problemas.

  • Os arquivos de regras precisam ser nomeados como baseline-prof.txt e colocados no diretório raiz do conjunto de origem principal. Ele precisa ser um arquivo irmão do AndroidManifest.xml.

  • Esses arquivos vão ser utilizados apenas se você estiver usando o Plug-in do Android para Gradle 7.1.0-alpha05 ou uma versão mais recente (Android Studio Bumblebee Canary 5).

  • No momento, o Bazel não oferece suporte à leitura e mesclagem de perfis de referência em um APK.

  • Perfis de referência compactados não podem ter mais de 1,5 MB. As bibliotecas e os aplicativos precisam definir um pequeno conjunto de regras de perfil que maximizem o impacto.

  • Regras abrangentes que compilem grande parte do aplicativo podem atrasar a inicialização devido ao aumento do acesso ao disco. Você precisa testar a performance dos perfis de referência.

Medir as melhorias

Automatizar a medição com a biblioteca Macrobenchmark

Macrobenchmarks permitem controlar a compilação de pré-medição usando a API CompilationMode, incluindo o uso de BaselineProfile.

Se você já tiver configurado um teste de BaselineProfileRule em um módulo da Macrobenchmark, vai poder definir um novo teste nesse módulo para avaliar a performance dele:

@RunWith(AndroidJUnit4::class)
class BaselineProfileBenchmark {
  @get:Rule
  val benchmarkRule = MacrobenchmarkRule()

  @Test
  fun startupNoCompilation() {
    startup(CompilationMode.None())
  }

  @Test
  fun startupBaselineProfile() {
    startup(CompilationMode.Partial(
      baselineProfileMode = BaselineProfileMode.Require
    ))
  }

  private fun startup(compilationMode: CompilationMode) {
    benchmarkRule.measureRepeated(
      packageName = "com.example.app",
      metrics = listOf(StartupTimingMetric()),
      iterations = 10,
      startupMode = StartupMode.COLD,
      compilationMode = compilationMode
    ) { // this = MacrobenchmarkScope
        pressHome()
        startActivityAndWait()
    }
  }
}

A Figura 2 mostra um exemplo de resultados de testes aprovados:

Figura 2. Esses são os resultados de um teste pequeno. Apps maiores vão ter benefícios maiores com os perfis de referência.

O exemplo acima analisa StartupTimingMetric, mas existem outras métricas importantes que precisam ser consideradas, como a instabilidade (métricas de frame), que podem ser medidas com a biblioteca Macrobenchmark do Jetpack.

Avaliar manualmente as melhorias do app

Para referência, primeiro vamos medir a inicialização do app não otimizada.

PACKAGE_NAME=com.example.app
# Force Stop App
adb shell am force-stop $PACKAGE_NAME
# Reset compiled state
adb shell cmd package compile --reset $PACKAGE_NAME
# Measure App startup
# This corresponds to `Time to initial display` metric
# For additional info https://developer.android.com/topic/performance/vitals/launch-time#time-initial
adb shell am start-activity -W -n $PACKAGE_NAME/.ExampleActivity \
 | grep "TotalTime"

A próxima etapa é carregar o perfil de referência.

# Unzip the Release APK first
unzip release.apk
# Create a ZIP archive
# Note: The name should match the name of the APK
# Note: Copy baseline.prof{m} and rename it to primary.prof{m}
cp assets/dexopt/baseline.prof primary.prof
cp assets/dexopt/baseline.profm primary.profm
# Create an archive
zip -r release.dm primary.prof primary.profm
# Confirm that release.dm only contains the two profile files:
unzip -l release.dm
# Archive:  release.dm
#   Length      Date    Time    Name
# ---------  ---------- -----   ----
#      3885  1980-12-31 17:01   primary.prof
#      1024  1980-12-31 17:01   primary.profm
# ---------                     -------
#                               2 files
# Install APK + Profile together
adb install-multiple release.apk release.dm

Para verificar se o pacote foi otimizado na instalação, execute o comando abaixo:

# Check dexopt state
adb shell dumpsys package dexopt | grep -A 1 $PACKAGE_NAME

A saída precisa declarar que o pacote foi compilado.

[com.example.app]
  path: /data/app/~~YvNxUxuP2e5xA6EGtM5i9A==/com.example.app-zQ0tkJN8tDrEZXTlrDUSBg==/base.apk
  arm64: [status=speed-profile] [reason=install-dm]

Agora, é possível medir a performance da inicialização do app como anteriormente, mas sem redefinir o estado compilado.

# Force Stop App
adb shell am force-stop $PACKAGE_NAME
# Measure App startup
adb shell am start-activity -W -n $PACKAGE_NAME/.ExampleActivity \
 | grep "TotalTime"

Problemas conhecidos

Atualmente, o uso de perfis de referência tem vários problemas conhecidos:

  • Os perfis de referência não são empacotados corretamente ao criar o APK de um pacote de apps. Para resolver esse problema, aplique com.android.tools.build:gradle:7.3.0-beta02 e versões mais recentes (problema).

  • Os perfis de referência são empacotados corretamente apenas para o arquivo classes.dex principal. Isso afeta apps com mais de um arquivo .dex. Para resolver esse problema, aplique com.android.tools.build:gradle:7.3.0-beta02 e versões mais recentes (problema).

  • A Macrobenchmark é incompatível com perfis de referência no Android 12L (API 32) (problema) e o Android 13 (API 33) (problema).

  • Não é permitido redefinir os caches de perfil do ART em builds user (sem acesso root). Para contornar isso, androidx.benchmark:benchmark-macro-junit4:1.1.0-rc02 inclui uma correção que reinstala o app durante a comparação (problema).

  • Os criadores de perfil do Android Studio não instalam perfis de referência ao criar o perfil do app (problema).

  • Atualmente, os sistemas de compilação que não são do Gradle (Bazel, Buck, etc.) não oferecem suporte à compilação de perfis de referência em APKs de saída.

  • A Play Store atualmente leva de 10 a 14 horas após o upload do AAB para disponibilizar os perfis de referência na instalação. Os usuários que fizerem o download do app durante esse período não vão aproveitar os benefícios desses perfis até que o dexopt seja executado em segundo plano, provavelmente durante a madrugada. Estamos trabalhando ativamente para melhorar isso.

  • Os canais de distribuição de apps que não são da Play Store podem não oferecer suporte ao uso de perfis de referência na instalação. Os usuários do app que usam esses canais não vão poder aproveitar os benefícios desses perfis até que o dexopt seja executado em segundo plano, provavelmente durante a madrugada.