API Performance Hint

Date de sortie:

Android 12 (niveau d'API 31) : API Performance Hint

Android 13 (niveau d'API 33) : Performance Hint Manager dans l'API NDK

(Preview) Android 15 (DP1) - reportActualWorkDuration()

Grâce aux indices de performances du processeur, un jeu peut influencer le comportement dynamique du processeur pour mieux répondre à ses besoins. Sur la plupart des appareils, Android ajuste de manière dynamique la vitesse d'horloge du processeur et le type de cœur d'une charge de travail en fonction des demandes précédentes. Si une charge de travail utilise davantage de ressources de processeur, la vitesse d'horloge augmente, et la charge de travail finit par être déplacée vers un cœur de plus grande taille. Si la charge de travail utilise moins de ressources, Android réduit l'allocation des ressources. Avec ADPF, l'application ou le jeu peuvent envoyer un signal supplémentaire concernant ses performances et ses délais. Cela permet au système de monter en puissance de manière plus agressive (amélioration des performances) et de réduire rapidement les horloges une fois la charge de travail terminée (économie d'énergie).

Vitesse d'horloge

Lorsque les appareils Android ajustent dynamiquement la vitesse d'horloge de leur processeur, la fréquence peut modifier les performances de votre code. La conception d'un code qui tient compte des vitesses d'horloge dynamiques est importante pour maximiser les performances, maintenir un état thermique sûr et utiliser l'énergie efficacement. Vous ne pouvez pas attribuer directement des fréquences de processeur dans le code de votre application. Par conséquent, un moyen courant pour les applications de tenter de s'exécuter à des vitesses d'horloge plus élevées consiste à exécuter une boucle occupée dans un thread d'arrière-plan afin que la charge de travail semble plus exigeante. Cette pratique est déconseillée, car elle gaspille de l'énergie et augmente la charge thermique de l'appareil lorsque l'application n'utilise pas les ressources supplémentaires. L'API CPU PerformanceHint est conçue pour résoudre ce problème. En indiquant au système la durée réelle du travail et la durée cible, Android pourra obtenir un aperçu des besoins de l'application en termes de processeur et allouer efficacement les ressources. Cela se traduit par des performances optimales à un niveau de consommation d'énergie efficace.

Types de cœurs

Les types de cœurs de processeur sur lesquels votre jeu s'exécute constituent un autre facteur de performances déterminant. Les appareils Android modifient souvent le cœur de processeur attribué à un thread de manière dynamique en fonction du comportement récent de la charge de travail. L'attribution des cœurs de processeur est encore plus complexe sur les SoC dotés de plusieurs types de cœurs. Sur certains de ces appareils, il n'est possible d'utiliser les cœurs de grande taille que brièvement pour ne pas passer à un état thermiquement intenable.

Il est déconseillé que votre jeu essaie de définir l'affinité du cœur de processeur pour les raisons suivantes :

  • Le type de cœur optimal pour une charge de travail varie selon le modèle de l'appareil.
  • La durabilité de l'exécution des cœurs de grande taille varie selon le SoC et les différentes solutions thermiques fournies par chaque modèle d'appareil.
  • L'impact environnemental sur l'état thermique peut compliquer davantage le choix des cœurs. Par exemple, la météo ou une coque de téléphone peut modifier l'état thermique d'un appareil.
  • La sélection des cœurs ne permet pas de prendre en charge de nouveaux appareils offrant des performances et des fonctionnalités thermiques supplémentaires. Par conséquent, les appareils ignorent souvent l'affinité du processeur d'un jeu.

Exemple de comportement par défaut du programmeur Linux

Comportement du programmeur Linux
Figure 1. Governor peut mettre environ 200 ms à augmenter ou diminuer la fréquence du processeur. ADPF fonctionne avec le système DVFS (Dynamic Voltage and Frequency Scaling) pour offrir des performances optimales en fonction du watt

L'API PerformanceHint fait plus que les latences DVFS.

ADPF fait davantage d'abstraction que les latences DVFS
Figure 2 : ADPF saura prendre les meilleures décisions pour vous
  • Si les tâches doivent être exécutées sur un processeur spécifique, l'API PerformanceHint sait comment prendre cette décision à votre place.
  • Par conséquent, vous n'avez pas besoin d'utiliser l'affinité.
  • Les appareils présentent différentes topologies. Les caractéristiques d'alimentation et thermiques sont trop variées pour être exposées au développeur d'applications.
  • Vous ne pouvez pas faire d'hypothèses sur le système sous-jacent sur lequel vous exécutez.

Solution

ADPF fournit la classe PerformanceHintManager afin que les jeux puissent envoyer des indices de performances à Android pour la vitesse d'horloge du processeur et le type de cœur. L'OS peut ainsi déterminer la meilleure façon d'utiliser ces indices en fonction du SoC et de la solution thermique de l'appareil. Si votre application utilise cette API avec la surveillance de l'état thermique, elle peut fournir des indices plus éclairés au système d'exploitation au lieu d'utiliser des boucles de disponibilité et d'autres techniques de codage pouvant entraîner des limitations.

Voici comment un jeu utilise les indices de performances :

  1. Créez des sessions d'indices pour les threads clés qui se comportent de la même manière. Exemple :
    • Le thread de rendu et ses dépendances reçoivent une seule session.
      1. Dans Cocos, le thread du moteur principal et le thread de rendu obtiennent une session.
      2. Dans Unity, intégrez le plug-in Adaptive Performance Android Provider.
      3. Dans Unreal, intégrez le plug-in Unreal Adaptive Performance et utilisez des options d'évolutivité pour accepter plusieurs niveaux de qualité.
    • Les threads d'E/S se voient attribuer une autre session.
    • Les threads audio se voient attribuer une troisième session.
  2. Le jeu doit effectuer cette tâche tôt, au moins 2 ms et de préférence plus de 4 ms avant qu'une session ne nécessite davantage de ressources système.
  3. Prévoyez la durée nécessaire pour exécuter chaque session pour chaque session d'indices. La durée typique est équivalente à un intervalle de frames, mais l'application peut utiliser un intervalle plus court si la charge de travail ne varie pas de manière significative d'un frame à l'autre.

Voici comment mettre la théorie en pratique:

Initialiser PerformanceHintManager et createHintSession

Obtenez le gestionnaire à l'aide du service système et créez une session d'indices pour votre thread ou groupe de threads travaillant sur la même charge de travail.

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

Définir des threads si nécessaire

Date de sortie:

Android 11 (niveau d'API 34)

Utilisez la fonction setThreads de PerformanceHintManager.Session lorsque vous devez ajouter d'autres threads ultérieurement. Par exemple, si vous créez votre thread physique ultérieurement et que vous devez l'ajouter à la session, vous pouvez utiliser cette 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);

Si vous ciblez des niveaux d'API inférieurs, vous devrez détruire la session et en recréer une chaque fois que vous devrez modifier les ID de thread.

Signaler la durée réelle des tâches

Suivez la durée réelle nécessaire à l'exécution du travail en nanosecondes et signalez-le au système à la fin du travail pour chaque cycle. Par exemple, si cela concerne vos threads de rendu, appelez-le sur chaque image.

Pour obtenir l'heure réelle de manière fiable, utilisez:

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

Par exemple :

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

Mettez à jour la durée de travail cible si nécessaire

Chaque fois que la durée cible de la tâche change, par exemple si le joueur choisit une autre fréquence d'images cible, appelez la méthode updateTargetWorkDuration pour informer le système afin que le système d'exploitation puisse ajuster les ressources en fonction de la nouvelle cible. Vous n'avez pas besoin de l'appeler sur chaque image et ne devez l'appeler que lorsque la durée cible change.

C++

APerformanceHint_updateTargetWorkDuration(hint_session, target_duration);

Java

hintSession.updateTargetWorkDuration(targetDuration);