API de Performance Hint

Lanzamiento:

Android 12 (nivel de API 31): API de Performance Hint

Android 13 (nivel de API 33): Administrador de sugerencias de rendimiento en la API del NDK

(versión preliminar) Android 15 (DP1): reportActualWorkDuration()

Con las sugerencias de rendimiento de la CPU, un juego puede influir en el rendimiento dinámico de la CPU el comportamiento de los usuarios para satisfacer mejor sus necesidades. En la mayoría de los dispositivos, Android se ajusta la velocidad del reloj de la CPU y el tipo de núcleo para una carga de trabajo según las demandas anteriores. Si una carga de trabajo usa más recursos de CPU, la velocidad del reloj aumenta y el y la carga de trabajo se trasladan a un núcleo más grande. Si la carga de trabajo usa menos recursos, Android reduce la asignación de recursos. Con ADPF, la aplicación o juego pueden enviar indicadores adicionales sobre su rendimiento y plazos. Esta Ayuda al sistema a aumentar la actividad de forma más agresiva (mejorar el rendimiento) y reducir rápidamente cuando la carga de trabajo se completa (ahorro de consumo de energía).

Velocidad de reloj

Cuando los dispositivos Android ajustan dinámicamente la velocidad del reloj de la CPU, la frecuencia puede cambiar el rendimiento de tu código. Cómo diseñar código que aborde el reloj dinámico es importante para maximizar el rendimiento, mantener y el uso eficiente de la energía. No puedes asignar frecuencias de CPU directamente en el código de tu aplicación. Como resultado, una forma común para que las apps intenten ejecutarse en niveles La velocidad del reloj de la CPU es ejecutar un bucle ocupado en un subproceso en segundo plano para que la carga de trabajo parece más exigente. Esta es una práctica no recomendada, ya que desperdicia energía y aumenta la carga térmica en el dispositivo cuando la app en realidad no está usando el de Google Cloud. La API de PerformanceHint de la CPU está diseñada para solucionar este problema. De indicando al sistema la duración real del trabajo y la duración objetivo del trabajo, Android podrá obtener una descripción general de las necesidades de CPU de la app y asignar los recursos de manera eficiente. Esto generará un rendimiento óptimo con una energía eficiente. de consumo de energía.

Tipos principales

Los tipos de núcleo de CPU en los que se ejecuta el juego son otro factor de rendimiento importante. Los dispositivos Android suelen cambiar el núcleo de CPU asignado a un subproceso de forma dinámica según el comportamiento reciente de la carga de trabajo. La asignación de núcleo de CPU es aún más compleja en SoCs con varios tipos de núcleo. En algunos de estos dispositivos, los núcleos más grandes solo se pueden usar brevemente sin pasar a un estado térmico insostenible.

El juego no debería intentar establecer la afinidad de núcleo de CPU por los siguientes motivos:

  • El mejor tipo de núcleo para una carga de trabajo varía según el modelo de dispositivo.
  • La sostenibilidad de ejecutar núcleos más grandes varía según el SoC y las diversas soluciones térmicas proporcionadas por cada modelo de dispositivo.
  • El impacto ambiental en el estado térmico puede complicar aún más la elección principal. Por ejemplo, el clima o una funda de teléfono pueden cambiar el estado térmico de un dispositivo.
  • La selección de núcleo no admite dispositivos nuevos con rendimiento y capacidades térmicas adicionales. Como resultado, los dispositivos a menudo ignoran la afinidad de procesador de un juego.

Ejemplo de comportamiento predeterminado del programador de Linux

Comportamiento de Linux Scheduler
Figura 1: El controlador puede tardar unos 200 ms en aumentar o disminuir la frecuencia de la CPU. El ADPF funciona con el sistema dinámico de escalamiento de frecuencia y voltaje (DVFS) para brindar el mejor rendimiento por vatio.

La API de PerformanceHint abstrae más que las latencias de DVFS

El ADPF abstrae más que las latencias de DVFS
Figura 2: El ADPF sabe cómo tomar la mejor decisión por ti.
  • Si las tareas deben ejecutarse en una CPU específica, la API de PerformanceHint sabe cómo tomar esa decisión en tu nombre.
  • Por lo tanto, no es necesario usar la afinidad.
  • Los dispositivos tienen varias topologías. Las características térmicas y de potencia son demasiado variadas para exponerlas al desarrollador de la app.
  • No puedes hacer suposiciones sobre el sistema subyacente en el que se ejecuta.

Solución

ADPF proporciona el PerformanceHintManager para que los juegos puedan enviar sugerencias de rendimiento a Android para conocer la velocidad del reloj de la CPU y el tipo de núcleo. Luego, el SO puede decidir la mejor manera de usar las sugerencias basadas en el SoC y la solución térmica del dispositivo. Si la app usa esta API junto con la supervisión de estado térmico, se puede proporcionar sugerencias más fundamentadas al SO en lugar de usar bucles ocupados y otras técnicas de codificación que puedan causar limitaciones.

Así es como un juego usa sugerencias de rendimiento:

  1. Crea sesiones de sugerencias para subprocesos clave que se comporten de manera similar. Por ejemplo:
  2. El juego debe hacer esto con tiempo, al menos 2 ms y preferentemente más de 4 ms antes de que una sesión necesite aumentar los recursos del sistema.
  3. En cada sesión de sugerencias, se predice la duración necesaria para que cada sesión se ejecute. La duración típica es equivalente a un intervalo de fotogramas, pero la app puede usar una a intervalos más cortos si la carga de trabajo no varía significativamente entre los fotogramas.

A continuación, te mostramos cómo poner la teoría en práctica:

Cómo inicializar PerformanceHintManager y createHintSession

Cómo obtener el administrador con el servicio del sistema y crear una sesión de sugerencias para tu subproceso o de subprocesos que trabaje en la misma carga de trabajo.

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);

Configura subprocesos si es necesario

Fecha de lanzamiento:

Android 11 (nivel de API 34)

Usa la setThreads. función de PerformanceHintManager.Session cuando tienes otros subprocesos que deben agregarse más adelante. Por ejemplo, si creas tu hilo de física y necesitas agregarla a la sesión, puedes usar esta API de 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);

Si apuntas a niveles de API inferiores, deberás destruir la sesión y vuelve a crear una sesión nueva cada vez que necesites cambiar los IDs de los subprocesos.

Informa la duración real del trabajo

Registra en nanosegundos la duración real necesaria para completar el trabajo y genera informes al sistema al finalizar el trabajo en cada ciclo. Por ejemplo, para subprocesos de renderización, llámalo en cada fotograma.

Para obtener la hora real de manera confiable, usa lo siguiente:

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();

Por ejemplo:

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);

Actualiza la duración objetivo del trabajo cuando sea necesario

Cada vez que cambie la duración objetivo del trabajo, por ejemplo, si el jugador elige una para diferentes FPS objetivo, llama a updateTargetWorkDuration para informarle al sistema, de modo que el SO pueda ajustar los recursos al nuevo objetivo. No es necesario que la llames en cada fotograma; solo debes llamarla cuando cambie la duración objetivo.

C++

APerformanceHint_updateTargetWorkDuration(hint_session, target_duration);

Java

hintSession.updateTargetWorkDuration(targetDuration);