Como criar uma comparação da Macrobenchmark

Use a biblioteca Macrobenchmark para testar casos de uso maiores do aplicativo, incluindo a inicialização do app e manipulações complexas de IU, como rolar uma RecyclerView ou mostrar animações. Se você quiser testar áreas menores do código, consulte a biblioteca Microbenchmark.

A biblioteca gera resultados comparativos no console do Android Studio e em um arquivo JSON com mais detalhes. Ela também fornece arquivos de rastreamento que podem ser carregados e analisados no Android Studio.

Use a biblioteca Macrobenchmark em um ambiente de integração contínua (CI, na sigla em inglês), conforme descrito em Executar comparações na integração contínua.

Os perfis de referência podem ser gerados com a biblioteca Macrobenchmark. Siga o guia abaixo para configurar a biblioteca e criar um perfil de referência.

Configuração do projeto

Recomendamos usar a Macrobenchmark com a versão mais recente do Android Studio, já que há novos recursos nessa versão do ambiente de desenvolvimento integrado à Macrobenchmark.

Configurar o módulo Macrobenchmark

A biblioteca Macrobenchmark exige um módulo com.android.test separado do código do app, responsável por executar os testes que medem o desempenho dele.

No Android Studio, há um modelo disponível para simplificar a configuração do módulo da biblioteca Macrobenchmark. O modelo de módulo de comparação cria automaticamente um módulo no projeto para medir o app criado por um módulo, incluindo um exemplo de inicialização de comparação.

Para usar o modelo e criar um novo módulo, siga estas etapas:

  1. Clique com o botão direito do mouse no seu projeto ou módulo no painel Project do Android Studio e clique em New > Module.

  2. Selecione Benchmark no painel Templates.

  3. É possível personalizar o aplicativo de destino (o app que vai ser comparado) e também o nome do pacote e do novo módulo da Macrobenchmark.

  4. Clique em Finish.

Modelo de módulo de comparação

Configurar o aplicativo

Para avaliar um app, que é chamado de destino da biblioteca Macrobenchmark, ele precisa ser profileable para ler informações detalhadas sobre o rastreamento sem afetar a performance. O assistente de módulo adiciona a tag <profileable> automaticamente ao arquivo AndroidManifest.xml do app.

Configure o app comparado para que fique o mais próximo possível da versão de lançamento (ou de produção). Defina-o como não depurável e, de preferência, com a minificação ativada, que melhora o desempenho. Normalmente, isso é feito criando uma cópia da variante da versão, que tem o mesmo desempenho, mas é assinada localmente com chaves de depuração. Como alternativa, use initWith para instruir o Gradle a fazer isso por você:

Groovy

buildTypes {
    release {
        minifyEnabled true
        shrinkResources true
        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    }

    benchmark {
        initWith buildTypes.release
        signingConfig signingConfigs.debug
        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'benchmark-rules.pro'
    }
}

Kotlin

buildTypes {
    getByName("release") {
        isMinifyEnabled = true
        isShrinkResources = true
        proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"))
    }

    create("benchmark") {
        initWith(getByName("release"))
        signingConfig = signingConfigs.getByName("debug")
    }
}

Sincronize com o Gradle, abra o painel Build Variants à esquerda e selecione a variante de comparação do app e do módulo da Macrobenchmark. Isso vai garantir que a comparação crie e teste a variante correta do app:

Selecionar variante de comparação

(Opcional) Configurar aplicativos com vários módulos

Caso seu app tenha mais de um módulo do Gradle, confira se os scripts de build sabem qual variante vai ser compilada. Sem isso, o buildType benchmark recém-adicionado faz com que o build falhe e mostra esta mensagem de erro:

> Could not resolve project :shared.
     Required by:
         project :app
      > No matching variant of project :shared was found.
      ...

Para corrigir o problema, adicione a propriedade matchingFallbacks no buildType benchmark dos seus módulos :macrobenchmark e :app. Os outros módulos do Gradle podem ter a mesma configuração anterior.

Groovy

benchmark {
    initWith buildTypes.release
    signingConfig signingConfigs.debug

    matchingFallbacks = ['release']
}

Kotlin

create("benchmark") {
    initWith(getByName("release"))
    signingConfig = signingConfigs.getByName("debug")

    matchingFallbacks += listOf('release')
}

Ao selecionar as variantes de build no seu projeto, escolha benchmark para os módulos :app e :macrobenchmark e release para qualquer outro módulo no app, como mostrado na imagem abaixo:

Variantes de comparação para projetos com vários módulos, com buildTypes de lançamento e de comparação selecionados

Para ver mais informações, consulte o gerenciamento de dependências com reconhecimento de variantes.

(Opcional) Configurar variações de produtos

Se você tem diversas variações de produto definidas no app, é necessário configurar o módulo :macrobenchmark para que ele saiba qual variação vai ser criada e comparada. Sem essa configuração, você pode receber um erro de build semelhante ao que acontece com vários módulos Gradle:

Could not determine the dependencies of task ':macrobenchmark:connectedBenchmarkAndroidTest'.
> Could not determine the dependencies of null.
   > Could not resolve all task dependencies for configuration ':macrobenchmark:benchmarkTestedApks'.
      > Could not resolve project :app.
        Required by:
            project :macrobenchmark
         > The consumer was configured to find a runtime of a component, as well as attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'benchmark', attribute 'com.android.build.api.attributes.AgpVersionAttr' with value '7.3.0'. However we cannot choose between the following variants of project :app:
             - demoBenchmarkRuntimeElements
             - productionBenchmarkRuntimeElements
           All of them match the consumer attributes:
           ...

Neste guia, usamos as duas variações de produto no módulo :app, demo e production, como você pode conferir neste snippet:

Groovy

flavorDimensions 'environment'
productFlavors {
    demo {
        dimension 'environment'
        // ...
    }

    production {
        dimension 'environment'
        // ...
    }
}

Kotlin

flavorDimensions += "environment"
productFlavors {
    create("demo") {
        dimension = "environment"
        // ...
    }
    create("production") {
        dimension = "environment"
        // ...
    }
}

Há duas maneiras de configurar a comparação com diversas variações de produto:

Usar missingDimensionStrategy

Especificar missingDimensionStrategy no defaultConfig do módulo :macrobenchmark instrui o sistema de compilação a substituir a dimensão de variações. Caso as dimensões não sejam encontradas no módulo, você precisa especificar quais vão ser usadas. No exemplo a seguir, a variação production é usada como a dimensão padrão.

Groovy

defaultConfig {
    missingDimensionStrategy "environment", "production"
}

Kotlin

defaultConfig {
    missingDimensionStrategy("environment", "production")
}

Dessa forma, o módulo :macrobenchmark só pode criar e comparar a variação de produto especificada, o que é útil quando você sabe que apenas uma delas tem a configuração adequada para comparação.

Definir variações de produtos no módulo :macrobenchmark

Para criar e comparar outras variações de produto, elas precisam ser definidas no módulo :macrobenchmark. Especifique-as de maneira semelhante ao módulo :app, mas atribua productFlavors a apenas uma dimension. Nenhuma outra configuração é necessária.

Groovy

flavorDimensions 'environment'
productFlavors {
    demo {
        dimension 'environment'
    }

    production {
        dimension 'environment'
    }
}

Kotlin

flavorDimensions += "environment"
productFlavors {
    create("demo") {
        dimension = "environment"
    }
    create("production") {
        dimension = "environment"
    }
}

Depois de definir e sincronizar o projeto, escolha a variante de build desejada no painel Build Variants:

Variantes de comparação do projeto com variações de produtos mostrando as opções "productBenchmark" e "release" selecionadas

Para saber mais, consulte Resolver erros de build relacionados à correspondência de variantes.

Criar uma classe de Macrobenchmark

O teste de comparação é fornecido pela API de regra MacrobenchmarkRule do JUnit4 na biblioteca Macrobenchmark. Ele contém um método measureRepeated que permite especificar várias condições de execução e comparar o aplicativo de destino.

É necessário especificar pelo menos o packageName do aplicativo de destino, quais metrics você quer medir e quantas iterations a comparação precisa executar.

Kotlin

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

    @Test
    fun startup() = benchmarkRule.measureRepeated(
        packageName = TARGET_PACKAGE,
        metrics = listOf(StartupTimingMetric()),
        iterations = 5,
        setupBlock = {
            // Press home button before each run to ensure the starting activity isn't visible.
            pressHome()
        }
    ) {
        // starts default launch activity
        startActivityAndWait()
    }
}

Java

@LargeTest
@RunWith(AndroidJUnit4.class)
public class SampleStartupBenchmark {
    @Rule
    public MacrobenchmarkRule benchmarkRule = new MacrobenchmarkRule();

    @Test
    public void startup() {
        benchmarkRule.measureRepeated(
            /* packageName */ TARGET_PACKAGE,
            /* metrics */ Arrays.asList(new StartupTimingMetric()),
            /* iterations */ 5,
            /* measureBlock */ scope -> {
                // starts default launch activity
                scope.startActivityAndWait();
                return Unit.INSTANCE;
            }
        );
    }
}

Para ver todas as opções de personalização da comparação, consulte a seção Personalizar as comparações.

Executar a comparação

Faça o teste no Android Studio para medir o desempenho do app no dispositivo. É possível executar as comparações da mesma forma que você executa qualquer outro @Test usando a ação de gutter ao lado da classe ou do método de teste, conforme mostrado na imagem abaixo.

Executar a macrobenchmark com ação de gutter ao lado da classe de teste

Também é possível fazer todas as comparações em um módulo do Gradle na linha de comando usando o comando connectedCheck:

./gradlew :macrobenchmark:connectedCheck

Ou para executar um único teste:

./gradlew :macrobenchmark:connectedCheck -P android.testInstrumentationRunnerArguments.class=com.example.macrobenchmark.startup.SampleStartupBenchmark#startup

Consulte a seção Comparações na CI para ver informações sobre como fazer e monitorar comparações na integração contínua.

Resultados da comparação

Após a execução da comparação, as métricas são exibidas diretamente no Android Studio e também são geradas para uso da CI em um arquivo JSON. Cada iteração medida captura um rastreamento do sistema separado. É possível abrir esses rastreamentos de resultados clicando em um dos links no painel Test Results, conforme mostrado na imagem abaixo.

Resultados de inicialização da Macrobenchmark

Quando o rastreamento é carregado, o Android Studio pede que você selecione o processo a ser analisado. A seleção é pré-preenchida com o processo do app de destino:

Seleção de processos de rastreamento do Studio

Após o arquivo de trace ser carregado, o Studio mostra os resultados na ferramenta CPU Profiler:

Rastreamento do Studio

Os relatórios JSON e todos os rastros de criação de perfil também são copiados automaticamente do dispositivo para o host. Eles são gravados na máquina host em:

project_root/module/build/outputs/connected_android_test_additional_output/debugAndroidTest/connected/device_id/

Acessar arquivos de rastreamento manualmente

Para usar a ferramenta Perfetto para analisar um arquivo de rastreamento, outras etapas precisam ser seguidas. O Perfetto possibilita inspecionar todos os processos que ocorrem em todo o dispositivo durante o rastreamento, enquanto o CPU Profiler do Android Studio limita a inspeção a um único processo.

Se você invocar os testes no Android Studio ou usar a linha de comando do Gradle, os arquivos de rastreamento vão ser copiados automaticamente do dispositivo para o host. Eles são gravados na máquina host em:

project_root/module/build/outputs/connected_android_test_additional_output/debugAndroidTest/connected/device_id/TrivialStartupBenchmark_startup[mode=COLD]_iter002.perfetto-trace

Quando o arquivo de rastreamento for criado no sistema host, você vai poder abri-lo no Android Studio no menu em File > Open. A tela da ferramenta do criador de perfil mostrada na seção anterior vai aparecer.

Erros de configuração

Se o app estiver configurado incorretamente (por exemplo, se for depurável ou não estiver relacionado à criação de perfil), a Macrobenchmark vai retornar um erro, em vez de relatar uma medição incorreta ou incompleta. É possível suprimir esses erros usando o argumento androidx.benchmark.suppressErrors.

Erros também são gerados ao tentar medir o desempenho em um emulador ou em um dispositivo com bateria fraca, porque isso pode comprometer a disponibilidade de núcleos e a velocidade de processamento.

Personalizar as comparações

A função measureRepeated aceita vários parâmetros que têm influência sobre as métricas que a biblioteca coleta, como o aplicativo é iniciado e compilado ou quantas iterações a comparação vai executar.

Capturar as métricas

As métricas são o principal tipo de informação extraída das comparações. As opções disponíveis são StartupTimingMetric, FrameTimingMetric e TraceSectionMetric. Para ver mais informações sobre eles, consulte a página Capturar as métricas.

Melhorar os dados de rastreamento com eventos personalizados

Pode ser útil instrumentar o aplicativo com eventos de rastreamento personalizados, que são mostrados com o restante do relatório de rastreamento e podem ajudar a apontar problemas específicos do app. Para saber mais sobre como criar eventos de rastreamento personalizados, consulte o guia Definir eventos personalizados.

CompilationMode

As comparações da Macrobenchmark podem especificar uma classe CompilationMode, que define quanto do app precisa ser pré-compilado do bytecode DEX (o formato de bytecode dentro de um APK) para código de máquina (semelhante à linguagem C++ pré-compilada).

Por padrão, as comparações da biblioteca Macrobenchmark são executadas com o CompilationMode.DEFAULT, que instala um perfil de referência (se disponível) no Android 7 (nível 24 da API) e versões mais recentes. Se você estiver usando o Android 6 (nível 23 da API) ou uma versão anterior, o modo de compilação compila o APK completamente como o comportamento padrão do sistema.

Você pode instalar um perfil de referência se o aplicativo de destino tiver um perfil de referência e usar a biblioteca ProfileInstaller.

No Android 7 e versões mais recentes, é possível personalizar o CompilationMode para afetar a quantidade de pré-compilação no dispositivo e imitar diferentes níveis de compilação antecipada (AOT, na sigla em inglês) ou armazenamento em cache JIT. Consulte CompilationMode.Full, CompilationMode.Partial e CompilationMode.None.

Essa funcionalidade é criada com base em comandos de compilação do ART. Cada comparação limpa os dados de perfil antes de começar para garantir a não interferência entre elas.

StartupMode

Para inicializar uma atividade, é possível transmitir um modo de inicialização predefinido (um de COLD, WARM ou HOT). Esse parâmetro muda a forma como a atividade é iniciada e o estado do processo no início do teste.

Para saber mais sobre os tipos de inicialização, consulte a documentação sobre inicialização do Android vitals.

Exemplos

Um projeto de exemplo está disponível como parte do repositório android/performance-samples (link em inglês) no GitHub.

Enviar feedback

Para informar problemas ou enviar solicitações de novos recursos para a Jetpack MacroBenchmark, consulte o Issue Tracker público.