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 adecuado 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
La API de PerformanceHint abstrae más que las latencias de DVFS
- 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 demasiado variados para exponerlos 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 sobre 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:
- Crea sesiones de sugerencias para subprocesos clave que se comporten de manera similar. Por ejemplo:
- El subproceso de renderización y sus dependencias obtienen una sesión.
- En Cocos, el subproceso principal del motor y el subproceso de renderización obtienen uno. sesión
- En Unity, integra el complemento del proveedor de Android para el rendimiento adaptable.
- En Unreal, integre el complemento de Unreal Adaptive Performance y úselo. Opciones de escalabilidad para admitir varios niveles de calidad
- Los subprocesos de IO obtienen otra sesión
- Los subprocesos de audio obtienen una tercera sesión.
- El subproceso de renderización y sus dependencias obtienen una sesión.
- 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.
- 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);