Introdução à biblioteca Jetpack Benchmark

A biblioteca Jetpack Benchmark permite que você compare rapidamente seu código baseado em Kotlin ou Java no Android Studio. A biblioteca lida com o aquecimento, mede o desempenho do código e as contagens de alocação e gera resultados comparativos no Console do Android Studio e em um arquivo JSON com mais detalhes.

Os casos de uso incluem rolagem de RecyclerView, execução de consultas de banco de dados e a medição de qualquer parte lenta do código que você quer acelerar.

É possível usar a biblioteca 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.

Se você ainda não adotou o AndroidX em um projeto que quer comparar, consulte Migrar um projeto existente usando o Android Studio.

Início rápido

Esta seção apresenta etapas rápidas para testar a comparação sem exigir que você transfira o código para os módulos. Como as etapas envolvem desativar a depuração de resultados de desempenho precisos, você não fará mudanças no sistema de controle de origem, mas elas ainda podem ser úteis quando quiser executar medições únicas.

Para realizar uma comparação única rapidamente, faça o seguinte:

  1. Adicione a biblioteca ao arquivo build.gradle do módulo:

    project_root/module_dir/build.gradle

    Groovy

    dependencies {
        androidTestImplementation 'androidx.benchmark:benchmark-junit4:1.1.0-alpha13'
    }
    

    Kotlin

    dependencies {
        androidTestImplementation("androidx.benchmark:benchmark-junit4:1.1.0-alpha13")
    }
    
  2. Para desativar a depuração no manifesto de teste, atualize o elemento <application> para forçar a desativação temporária da depuração da seguinte maneira:

    project_root/module_dir/src/androidTest/AndroidManifest.xml

    <!-- Important: disable debuggable for accurate performance results -->
    <application
        android:debuggable="false"
        tools:ignore="HardcodedDebugMode"
        tools:replace="android:debuggable"/>
    
  3. Para adicionar sua comparação, inclua uma instância de BenchmarkRule em um arquivo de teste no diretório androidTest. Para mais informações sobre como programar comparações, consulte Como programar uma comparação.

    O snippet de código a seguir mostra como adicionar uma comparação a um teste do JUnit:

    Kotlin

    @RunWith(AndroidJUnit4::class)
    class MyBenchmark {
        @get:Rule
        val benchmarkRule = BenchmarkRule()
    
        @Test
        fun benchmarkSomeWork() = benchmarkRule.measureRepeated {
            doSomeWork()
        }
    }
    

    Java

    @RunWith(AndroidJUnit4.class)
    class MyBenchmark {
        @Rule
        public BenchmarkRule benchmarkRule = new BenchmarkRule();
    
        @Test
        public void benchmarkSomeWork() {
            final BenchmarkState state = benchmarkRule.getState();
            while (state.keepRunning()) {
                doSomeWork();
            }
        }
    }
    

Quando fazer a comparação

É recomendável criar o perfil do código antes de programar uma comparação. Isso ajuda você a encontrar operações caras que valem a pena otimizar. Ela também pode esclarecer por que as operações estão lentas, mostrando o que está acontecendo enquanto elas são executadas. Exemplos podem incluir: execução em uma linha de execução de baixa prioridade, suspensão devido ao acesso ao disco ou chamada inesperada em uma função cara, como decodificação de bitmap.

A adição de pontos de rastreamento personalizados por meio da API TraceCompat, ou do wrapper -ktx, permite visualizá-los na CPU do Android Studio Profiler ou Systrace:

Kotlin

fun proccessBitmap(bitmap: Bitmap): Bitmap {
    trace("processBitmap") {
        // perform work on bitmap...
    }
}

Java

public Bitmap processBitmap(Bitmap bitmaptest) {
    TraceCompat.beginSection("processBitmap");
    try {
        // perform work on bitmap...
    } finally {
        TraceCompat.endSection();
    }
}

Em que usar a comparação

As comparações são úteis principalmente para trabalhos da CPU que são executados no app muitas vezes. Bons exemplos são a rolagem de RecyclerView, conversão/processamento de dados e trechos de código que são usados repetidamente.

É mais difícil medir outros tipos de código com uma comparação. Como as comparações são executadas em loop, qualquer código que não seja executado com frequência ou que apresente desempenhos diferentes quando chamado várias vezes pode não funcionar para a comparação.

Armazenamento em cache

Tente evitar medir apenas o cache. Por exemplo, uma comparação para o layout de uma visualização personalizada pode medir apenas o desempenho do cache de layout. Para evitar isso, é possível transmitir diferentes parâmetros de layout em cada loop. Isso pode ser difícil em outros casos, como ao medir o desempenho do sistema de arquivos, porque o SO armazena o sistema de arquivos em cache durante o loop.

Código pouco executado

É pouco provável que a compilação JIT pelo Android Runtime (ART) ocorra para um código que é executado apenas uma vez durante a inicialização do aplicativo. Por esse motivo, fazer a comparação desse código com o microbenchmark, em que ele é executado em um loop restrito, não é uma forma realista de medir o desempenho.

Para comparar esse tipo de código, recomendamos o Jetpack Macrobenchmark, que permite medir interações de usuário de nível mais alto, como a inicialização do app e o desempenho de rolagem.

Configuração completa do projeto

Para configurar a comparação como habitual em vez de única, isole as comparações no módulo delas. Isso garante que a configuração, como definir debuggable como false, é diferente dos testes regulares.

Antes de adicionar seu módulo de comparação, coloque o código e os recursos que você quer comparar em um módulo de biblioteca, se eles ainda não estiverem em um.

Nossas amostras apresentam exemplos de como configurar um projeto dessa forma.

Configurar as propriedades do Android Studio

Se você estiver usando o Android Studio 3.5, será necessário definir as propriedades do Android Studio manualmente para ativar a compatibilidade com o assistente de módulo de comparação. Isso não é necessário para o Android Studio 3.6 ou versões mais recentes.

Para ativar o modelo do Android Studio para uma comparação, faça o seguinte:

  1. No Android Studio 3.5, clique em Help > Edit Custom Properties.

  2. Adicione a seguinte linha ao arquivo que será aberto:

    npw.benchmark.template.module=true

  3. Salve e feche o arquivo.

  4. Reinicie o Android Studio.

Criar um novo módulo

O modelo do módulo de comparação define automaticamente as configurações da comparação.

Para usar o modelo do módulo para criar um novo módulo, siga as seguintes etapas:

  1. Clique com o botão direito do mouse no seu projeto ou módulo e selecione New > Module.

  2. Selecione Benchmark Module e clique em Next.

    Figura 1. Módulo de comparação.

  3. Insira um nome para o módulo, escolha o idioma e clique em Finish.

    Um módulo pré-configurado para comparação será criado, com um diretório de comparação adicionado e debuggable definido como false.

Escrever uma comparação

As comparações são testes de instrumentação padrão. Para criar uma comparação, use a classe BenchmarkRule oferecida pela biblioteca. Para comparar atividades, use ActivityTestRule ou ActivityScenarioRule. Para comparar o código da IU, use @UiThreadTest.

O código a seguir apresenta uma amostra de comparação:

Kotlin

@RunWith(AndroidJUnit4::class)
class ViewBenchmark {
    @get:Rule
    val benchmarkRule = BenchmarkRule()

    @Test
    fun simpleViewInflate() {
        val context = ApplicationProvider.getApplicationContext<Context>()
        val inflater = LayoutInflater.from(context)
        val root = FrameLayout(context)

        benchmarkRule.measureRepeated {
            inflater.inflate(R.layout.test_simple_view, root, false)
        }
    }
}

Java

@RunWith(AndroidJUnit4::class)
public class ViewBenchmark {
    @Rule
    public BenchmarkRule benchmarkRule = new BenchmarkRule();

    @Test
    public void simpleViewInflate() {
        Context context = ApplicationProvider.getApplicationContext<Context>();
        final BenchmarkState state = benchmarkRule.getState();
        LayoutInflater inflater = LayoutInflater.from(context);
        FrameLayout root = new FrameLayout(context);

        while (state.keepRunning()) {
            inflater.inflate(R.layout.test_simple_view, root, false);
        }
    }
}

Você pode desativar a medição de tempo em seções de código que você não queira avaliar, conforme mostrado na amostra de código a seguir:

Kotlin

@Test
fun bitmapProcessing() = benchmarkRule.measureRepeated {
    val input: Bitmap = runWithTimingDisabled { constructTestBitmap() }
    processBitmap(input)
}

Java

@Test
public void bitmapProcessing() {
    final BenchmarkState state = benchmarkRule.getState();
    while (state.keepRunning()) {
        state.pauseTiming();
        Bitmap input = constructTestBitmap();
        state.resumeTiming();

        processBitmap(input);
    }
}

Para saber mais sobre como executar uma comparação, consulte Executar a comparação.

Executar a comparação

No Android Studio, execute a comparação da mesma forma que faria com qualquer @Test. No Android Studio 3.4 e versões mais recentes, é possível ver a saída enviada para o console.

Para executar a comparação, vá até benchmark/src/androidTest no módulo e pressione Control+Shift+F10 (Command+Shift+R no Mac). Os resultados da comparação aparecem no console, conforme mostrado na Figura 2:

Saída de comparação no Android Studio

Figura 2. Saída de comparação no Android Studio

Na linha de comando, execute o connectedCheck comum:

./gradlew benchmark:connectedCheck

Coletar dados

Um relatório de comparação completo com mais métricas e informações sobre o dispositivo está disponível em formato JSON.

Por padrão, o plug-in para Gradle androidx.benchmark ativa a saída JSON. Para ativar a saída JSON manualmente em ambientes de criação que não sejam o Gradle, transmita um argumento de instrumentação, androidx.benchmark.output.enable, definido como true.

Veja a seguir um exemplo usando o comando adb shell am instrument:

adb shell am instrument -w -e "androidx.benchmark.output.enable" "true" com.android.foo/androidx.benchmark.junit4.AndroidBenchmarkRunner

Por padrão, o relatório JSON é gravado no disco do dispositivo na pasta de downloads compartilhados externos do APK de teste, que normalmente fica localizada em:

/storage/emulated/0/Download/app_id-benchmarkData.json

É possível configurar o local em que os relatórios de comparação serão salvos no dispositivo usando o argumento de instrumentação additionalTestOutputDir.

adb shell am instrument -w -e "androidx.benchmark.output.enable" "true" -e "additionalTestOutputDir" "/path_to_a_directory_on_device_test_has_write_permissions_to/" com.android.foo/androidx.benchmark.junit4.AndroidBenchmarkRunner

Os relatórios JSON são copiados 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/app_id-benchmarkData.json

Plug-in do Android para Gradle 3.6 e versões mais recentes

Ao executar comparações na linha de comando com o Gradle, os projetos que usam o Plug-in do Android para Gradle 3.6 e versões mais recentes podem adicionar a seguinte sinalização ao arquivo gradle.properties do projeto:

android.enableAdditionalTestOutput=true

Isso permitirá que um recurso experimental do Plug-in do Android para Gradle extraia relatórios de comparação de dispositivos com API 16 e versões mais recentes.

Plug-in do Android para Gradle 3.5 e versões anteriores

Para usuários de versões mais antigas do Plug-in do Android para Gradle, é o plug-in androidx.benchmark para Gradle que copia os relatórios de comparação JSON do dispositivo para o host.

Ao usar o AGP 3.5 ou versões anteriores, voltado à API de nível 29 ou mais recente, é necessário adicionar uma sinalização ao manifesto do Android no diretório androidTest das comparações para ativar o comportamento de armazenamento externo legado.

<manifest ... >
  <!-- This attribute is "false" by default on apps targeting Android 10. -->
  <application android:requestLegacyExternalStorage="true" ... >
    ...
  </application>
</manifest>

Para ver mais informações, consulte Desativar temporariamente o armazenamento com escopo.

Estabilidade do relógio

Os relógios em dispositivos móveis mudam dinamicamente do estado alto (para maior desempenho) para o estado baixo (para economizar energia ou quando o dispositivo esquenta). Esses relógios variados podem fazer com que os números de comparação variem muito. Por esse motivo, a biblioteca oferece maneiras de lidar com esse problema.

Bloquear os relógios (acesso root necessário)

Bloquear os relógios é a melhor maneira de alcançar um desempenho estável. Isso garante que os relógios nunca cheguem a um estado alto o suficiente para aquecer o dispositivo ou um estado baixo caso uma comparação não esteja utilizando a CPU totalmente. Embora essa seja a melhor forma de garantir um desempenho estável, ela não é compatível com a maior parte dos dispositivos, porque exige o acesso root ao adb.

Para bloquear seus relógios, adicione o plug-in de ajuda fornecido ao caminho de classe de nível mais alto do projeto no arquivo build.gradle principal:

Groovy

buildscript {
    ...
    dependencies {
        ...
        classpath "androidx.benchmark:benchmark-gradle-plugin:1.1.0-alpha13"
    }
}

Kotlin

buildscript {
    ...
    dependencies {
        ...
        classpath("androidx.benchmark:benchmark-gradle-plugin:1.1.0-alpha13")
    }
}

Aplique o plug-in no build.gradle do seu módulo que está sendo comparado:

Groovy

plugins {
    id 'com.android.app'
    id 'androidx.benchmark'
}
...

Kotlin

plugins {
    id("com.android.app")
    id("androidx.benchmark")
    ...
}

Isso adiciona as tarefas de comparação do Gradle ao seu projeto, incluindo ./gradlew lockClocks e ./gradlew unlockClocks. Use essas tarefas para bloquear e desbloquear a CPU do dispositivo usando o adb.

Se você tiver vários dispositivos visíveis para o adb, use a variável de ambiente ANDROID_SERIAL para especificar em qual dispositivo a tarefa Gradle deve operar:

ANDROID_SERIAL=device-id-from-adb-devices ./gradlew lockClocks

Modo de desempenho sustentado

Window.setSustainedPerformanceMode() é um recurso compatível com alguns dispositivos que permite que um app opte por uma frequência máxima de CPU mais baixa. Quando executada em dispositivos compatíveis, a biblioteca Benchmark usa uma combinação dessa API e inicia a própria atividade para impedir a limitação térmica e estabilizar os resultados.

Essa função é ativada por padrão pelo testInstrumentationRunner definido pelo plug-in para Gradle. Caso queira usar um executor personalizado, você pode criar uma subclasse AndroidBenchmarkRunner e usá-lo como seu testInstrumentationRunner.

O executor lança uma atividade opaca em tela cheia para garantir que a comparação seja executada em primeiro plano e sem qualquer outro app drenando a bateria.

Pausar a execução automática

Se nem o bloqueio de relógio nem o desempenho sustentado forem usados, a biblioteca executa a detecção de limitação térmica automaticamente. Quando ativada, a comparação interna é executada periodicamente para determinar quando a temperatura do dispositivo ficou alta o suficiente para diminuir o desempenho da CPU. Quando um baixo desempenho da CPU for detectado, a biblioteca pausará a execução para permitir que o dispositivo resfrie e repetirá a comparação atual.

Erros de configuração

A biblioteca detecta as condições a seguir para garantir que o projeto e o ambiente estejam configurados de modo a apresentar um desempenho preciso para a versão:

  • Debuggable é definido como false.
  • Um dispositivo físico está sendo utilizado, e não um emulador.
  • Os relógios estão bloqueados, caso o dispositivo tenha acesso root.
  • O dispositivo está com um nível de bateria suficiente.

Se qualquer uma das verificações acima apresentar uma falha, a comparação gerará um erro para evitar medições imprecisas.

Para suprimir esses erros no formato de avisos e impedir que interrompam a comparação, transmita o tipo de erros que você quer suprimir em uma lista separada por vírgulas para o argumento de instrumentação androidx.benchmark.suppressErrors:

adb shell am instrument -w -e "androidx.benchmark.suppressErrors" "DEBUGGABLE" com.android.foo/androidx.benchmark.junit4.AndroidBenchmarkRunner

Você pode definir essa lista no Gradle da seguinte forma:

Groovy

android {
    defaultConfig {
        testInstrumentationRunnerArgument 'androidx.benchmark.suppressErrors', 'DEBUGGABLE'
    }
}

Kotlin

android {
    defaultConfig {
        testInstrumentationRunnerArguments(mapOf(
            "androidx.benchmark.suppressErrors" to "DEBUGGABLE"
        ))
    }
}

Suprimir erros permite que a comparação seja executada em uma configuração incorreta, mas a saída da comparação será corrompida ao incluir intencionalmente os erros como prefixos dos nomes de testes. Ou seja, quando uma comparação depurável for executada com a supressão acima, os nomes de testes receberão DEBUGGABLE_ como prefixo.

Caracterização de perfil

Você pode criar um perfil de uma comparação para investigar por que o código medido está lento.

Adicione o seguinte ao arquivo build.gradle do seu módulo de comparação:

Groovy

android {
    defaultConfig {
        // must be one of: 'None', 'StackSampling', or 'MethodTracing'
        testInstrumentationRunnerArgument 'androidx.benchmark.profiling.mode', 'StackSampling'
    }
}

Kotlin

android {
    defaultConfig {
        // must be one of: 'None', 'StackSampling', or 'MethodTracing'
        testInstrumentationRunnerArgument = mapOf(
            "androidx.benchmark.profiling.mode", "StackSampling"
        )
    }
}

Quando você executa uma comparação, um arquivo de saída .trace é copiado para o host, no diretório ao lado dos resultados JSON. Abra esse arquivo com o Android Studio usando File > Open para inspecionar os resultados de criação de perfil no CPU Profiler.

Rastreamento de métodos

Com o rastreamento de métodos, a comparação aquecerá antes de capturar o rastreamento de um método, registrando todos os métodos chamados pela própria comparação. Os resultados de desempenho serão afetados significativamente pela sobrecarga da captura de cada entrada/saída do método.

Amostragem de pilhas

Com a amostragem de pilhas, a comparação coletará amostras das pilhas de chamadas após a conclusão do aquecimento. É possível controlar a frequência e a duração da amostragem usando argumentos de instrumentação.

No Android 10 (API 29) e versões mais recentes, a amostragem de pilhas usa o Simpleperf para amostragens de pilhas de chamadas do app, incluindo o código C++. No Android 9 (API 28) e versões anteriores, ela usa Debug.startMethodTracingSampling para capturar amostras de pilha.

Para ler mais sobre como usar o rastreamento e a amostragem de métodos, consulte a configuração de criação de perfil da CPU.

Amostras de comparação

Amostras de código de comparação estão disponíveis nos seguintes projetos:

As amostras de projetos incluem:

  • BenchmarkSample: essa é uma amostra independente, que apresenta como usar um módulo de comparação para medir o código e a IU.

  • PagingWithNetworkSample: amostra dos Componentes da arquitetura do Android, que mostra como fazer uma comparação do desempenho do RecyclerView.

  • WorkManagerSample: amostra dos Componentes da arquitetura do Android, que mostra como fazer uma comparação de workers do WorkManager.

Outros recursos

Para saber mais sobre a comparação, consulte os recursos a seguir.

Blogs

Enviar feedback

Para comunicar problemas ou enviar solicitações de recursos usando a comparação, consulte o rastreador de problemas público.