Lançado:
Android 12 (nível 31 da API): API Performance Hint
Android 13 (nível 33 da API): Performance Hint Manager na API NDK
(Prévia) Android 15 (DP1): reportActualWorkDuration()
Com as dicas de performance da CPU, um jogo pode influenciar o comportamento dinâmico da 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 uma carga de trabalho usar mais recursos da CPU, a velocidade do clock vai aumentar e a carga de trabalho será movida para um núcleo maior. Se a carga de trabalho usar menos recursos, o Android vai diminuir a alocação deles. Com o ADPF, o aplicativo ou jogo pode enviar um sinal adicional sobre a performance e os prazos. Isso ajuda o sistema a aumentar a velocidade de forma mais agressiva (melhorando o desempenho) e a diminuir os clocks rapidamente quando a carga de trabalho é concluída (economizando energia).
Velocidade do relógio
Quando os dispositivos Android ajustam dinamicamente a velocidade do relógio da CPU, a frequência pode
mudar o desempenho do seu 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, já que 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 desejada do trabalho,
o Android consegue uma visão geral das necessidades de CPU do app e aloca
recursos de maneira 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 jogo é 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 jogo 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 jogo.
Exemplo do comportamento padrão do agendador do Linux

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

- Se as tarefas precisarem ser executadas em uma CPU específica, a API PerformanceHint saberá como tomar essa decisão por você.
- Portanto, não é necessário usar a afinidade.
- Os dispositivos vêm com várias topologias, e 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 em que você está executando.
Solução
O ADPF fornece a classe PerformanceHintManager
para que os jogos possam enviar dicas de desempenho ao Android em relação à velocidade do clock 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.
Veja como um jogo usa as dicas de performance:
- Crie sessões de dicas para linhas de execução importantes que se comportam de forma semelhante. Por exemplo:
- A linha de execução de renderização e as dependências dela recebem uma sessão
- No Cocos, a linha de execução principal do mecanismo e a linha de execução de renderização recebem uma sessão
- No Unity, integre o plug-in Adaptive Performance Android Provider.
- No Unreal, integre o plug-in Unreal Adaptive Performance e use as Opções de escalonabilidade para oferecer suporte a vários níveis de qualidade.
- As linhas de execução de E/S recebem outra sessão
- As linhas de execução de áudio recebem uma terceira sessão
- A linha de execução de renderização e as dependências dela recebem uma sessão
- O jogo precisa fazer isso pelo menos 2ms ou, de preferência, mais de 4ms antes de uma sessão exigir mais recursos do sistema.
- Estima a duração necessária para cada sessão de dica. A duração típica é equivalente a um intervalo de frames, mas o app pode usar um intervalo menor caso a carga de trabalho não varie significativamente entre frames.
Veja como colocar a teoria em prática:
Inicializar o PerformanceHintManager e criar o createHintSession
Receba o gerenciador usando o serviço do sistema e crie uma sessão de dica 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);
Defina as linhas de execução, se necessário
Lançado:
Android 11 (nível 34 da API)
Use a função setThreads
do PerformanceHintManager.Session
quando tiver outras linhas de execução
que precisam ser adicionadas depois. Por exemplo, se você criar sua linha de execução de física
mais tarde e precisar adicioná-la à sessão, use 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 de API mais baixos, será necessário destruir a sessão e recriar uma nova sempre que precisar mudar os IDs das linhas 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 todos os frames.
Para receber a hora 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 desejada do trabalho quando necessário
Sempre que a duração do trabalho desejada mudar, por exemplo, se o jogador escolher uma
taxa de frames por segundo (fps) diferente, chame o método updateTargetWorkDuration
para informar ao sistema e permitir que o SO ajuste os recursos de acordo
com a nova meta. Não é necessário chamar a função em todos os frames. Basta fazer isso quando a duração desejada mudar.
C++
APerformanceHint_updateTargetWorkDuration(hint_session, target_duration);
Java
hintSession.updateTargetWorkDuration(targetDuration);