API Performance Hint

Lançado:

Android 12 (nível 31 da API) - PerformanceHintManager

Android 13 (nível 33 da API) - Performance Hint Manager na API NDK

Android 15 (DP1) - reportActualWorkDuration()

Com as dicas de performance da CPU, um app pode influenciar o comportamento dinâmico de performance da CPU para atender melhor às necessidades dele. Na maioria dos dispositivos, o Android ajusta dinamicamente a velocidade do relógio e o tipo de núcleo da CPU de uma carga de trabalho com base nas demandas anteriores. Se a carga usar mais recursos da CPU, a velocidade do relógio vai aumentar e a carga de trabalho será movida para um núcleo maior. Se a carga usar menos recursos, o Android vai diminuir a alocação deles. Com o ADPF, um app pode enviar um sinal adicional sobre a performance e os prazos. Isso ajuda o sistema a aumentar a velocidade de forma mais agressiva (melhorando a performance) e a diminuir os relógios rapidamente quando a carga de trabalho é concluída (economizando o uso de energia).

Velocidade do relógio

Quando os dispositivos Android ajustam dinamicamente a velocidade do relógio da CPU, a frequência pode mudar a performance do código. É importante projetar um código que aborde a velocidade dinâmica do relógio para maximizar a performance, manter um estado térmico seguro e usar a energia de forma eficiente. Não é possível atribuir frequências de CPU diretamente no código do app. Como resultado, uma maneira comum de os apps tentarem ser executados em velocidades mais altas do relógio da CPU é com um loop ocupado em uma linha de execução em segundo plano. Assim, a carga de trabalho parece mais exigente. Essa é uma prática ruim, porque desperdiça energia e aumenta a carga térmica no dispositivo quando o app não está usando os recursos extras. A API PerformanceHint da CPU foi projetada para resolver esse problema. Ao informar ao sistema a duração real e a duração de trabalho de destino, o Android poderá ter uma visão geral das necessidades de CPU do app e alocar recursos de forma eficiente. Isso vai levar a uma performance ideal com um nível de consumo de energia eficiente.

Tipos de núcleo

Os tipos de núcleo da CPU em que seu app é executado são outro fator de performance importante. Os dispositivos Android geralmente mudam o núcleo da CPU atribuído a uma linha de execução de forma dinâmica com base no comportamento recente da carga de trabalho. A atribuição de núcleo da CPU é ainda mais complexa em SoCs com vários tipos de núcleo. Em alguns desses dispositivos, os núcleos maiores só podem ser usados de forma breve, porque atingem rapidamente um estado térmico insustentável.

Seu app não deve tentar definir a afinidade de núcleo da CPU pelos seguintes motivos:

  • O melhor tipo de núcleo para a carga de trabalho varia de acordo com o modelo do dispositivo.
  • A sustentabilidade da execução de núcleos maiores varia de acordo com o SoC e as diversas soluções térmicas fornecidas por cada modelo de dispositivo.
  • O impacto ambiental no estado térmico pode complicar ainda mais a escolha do núcleo. Por exemplo, o clima ou uma capa de smartphone pode mudar o estado térmico de um dispositivo.
  • A seleção do núcleo não acomoda novos dispositivos com recursos térmicos e de performance extras. Como resultado, os dispositivos geralmente ignoram a afinidade entre o processador e um app.

Exemplo de comportamento padrão do agendador do Linux

Comportamento do programador do Linux
Figura 1. O governador pode levar cerca de 200 ms para aumentar ou diminuir a frequência da CPU. O ADPF funciona com o sistema de escalonamento dinâmico de tensão e frequência (DVFS, na sigla em inglês) para oferecer a melhor performance por watt

A API PerformanceHint abstrai mais do que latências de DVFS

A ADPF abstrai mais do que latências de DVFS
Figura 2. O ADPF sabe como tomar a melhor decisão em seu nome
  • Se as tarefas precisarem ser executadas em uma CPU específica, a API PerformanceHint saberá como tomar essa decisão em seu nome.
  • Portanto, não é necessário usar a afinidade.
  • Os dispositivos vêm com várias topologias. As características térmicas e de energia são muito variadas para serem expostas ao desenvolvedor de apps.
  • Não é possível fazer suposições sobre o sistema subjacente em que você está executando.

Solução

O ADPF fornece a PerformanceHintManager classe para que os apps possam enviar dicas de performance ao Android em relação à velocidade do relógio e ao tipo de núcleo da CPU. Então, o SO pode decidir a melhor forma de usar as dicas com base no SoC e na solução térmica do dispositivo. Se o app usa essa API com o monitoramento de estado térmico, ele pode fornecer dicas mais fundamentadas para o SO, em vez de usar loops ocupados e outras técnicas de programação que podem causar limitações.

Confira como colocar a teoria em prática:

Inicializar PerformanceHintManager e createHintSession

Receba o gerenciador usando o serviço do sistema e crie uma sessão de dicas para sua linha de execução ou grupo de linhas de execução que trabalham na mesma carga de trabalho.

C++

int32_t tids[1];
tids[0] = gettid();
int64_t target_fps_nanos = getFpsNanos();
APerformanceHintManager* hint_manager = APerformanceHint_getManager();
APerformanceHintSession* hint_session =
  APerformanceHint_createSession(hint_manager, tids, 1, target_fps_nanos);

Java

int[] tids = {
  android.os.Process.myTid()
};
long targetFpsNanos = getFpsNanos();
PerformanceHintManager performanceHintManager =
  (PerformanceHintManager) this.getSystemService(Context.PERFORMANCE_HINT_SERVICE);
PerformanceHintManager.Session hintSession =
  performanceHintManager.createHintSession(tids, targetFpsNanos);

Definir linhas de execução, se necessário

Lançado:

Android 11 (nível 34 da API)

Use a setThreads função do PerformanceHintManager.Session quando tiver outras linhas de execução que precisam ser adicionadas mais tarde. Por exemplo, se você criar a linha de execução de física mais tarde e precisar adicioná-la à sessão, poderá usar essa API setThreads.

C++

auto tids = thread_ids.data();
std::size_t size = thread_ids_.size();
APerformanceHint_setThreads(hint_session, tids, size);

Java

int[] tids = new int[3];

// add all your thread IDs. Remember to use android.os.Process.myTid() as that
// is the linux native thread-id.
// Thread.currentThread().getId() will not work because it is jvm's thread-id.
hintSession.setThreads(tids);

Se você estiver segmentando níveis mais baixos da API, será necessário destruir a sessão e criar uma nova sempre que precisar mudar os IDs da linha de execução.

Informar a duração real do trabalho

Acompanhe a duração real necessária para concluir o trabalho em nanossegundos e informe ao sistema após a conclusão do trabalho em cada ciclo. Por exemplo, se isso for para suas linhas de execução de renderização, chame isso em cada frame.

Para receber o horário real de forma confiável, use:

C++

clock_gettime(CLOCK_MONOTONIC, &clock); // if you prefer "C" way from <time.h>
// or
std::chrono::high_resolution_clock::now(); // if you prefer "C++" way from <chrono>

Java

System.nanoTime();

Exemplo:

C++

// All timings should be from `std::chrono::steady_clock` or `clock_gettime(CLOCK_MONOTONIC, ...)`
auto start_time = std::chrono::high_resolution_clock::now();

// do work

auto end_time = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::nanoseconds>(end_time - start_time).count();
int64_t actual_duration = static_cast<int64_t>(duration);

APerformanceHint_reportActualWorkDuration(hint_session, actual_duration);

Java

long startTime = System.nanoTime();

// do work

long endTime = System.nanoTime();
long duration = endTime - startTime;

hintSession.reportActualWorkDuration(duration);

Atualizar a duração do trabalho de destino quando necessário

Sempre que a duração do trabalho de destino mudar, por exemplo, se o jogador escolher um fps de destino diferente, chame o updateTargetWorkDuration método para informar o sistema para que o SO possa ajustar os recursos de acordo com o novo destino. Não é necessário chamá-lo em cada frame, apenas quando a duração de destino mudar.

C++

APerformanceHint_updateTargetWorkDuration(hint_session, target_duration);

Java

hintSession.updateTargetWorkDuration(targetDuration);