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

Avec les indices de performances du processeur, un jeu peut influencer les performances dynamiques du processeur pour mieux répondre à ses besoins. Sur la plupart des appareils, Android ajuste dynamiquement 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, charge de travail est finalement déplacée vers un cœur plus important. Si la charge de travail utilise moins ressources, Android réduit l'allocation des ressources. Avec ADPF, l'application ou jeu peuvent envoyer un signal supplémentaire sur ses performances et ses délais. Ce aide le système à monter en puissance plus agressivement (en améliorant les performances) et à réduire horloge rapide lorsque la charge de travail est 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. Concevoir du code qui cible l'horloge dynamique est importante pour maximiser les performances et maintenir une température et une utilisation efficace de l'énergie. Vous ne pouvez pas attribuer directement des fréquences de processeur dans le code de votre application. Par conséquent, une méthode courante pour les applications de tenter de s'exécuter à un niveau Les vitesses d'horloge du processeur consistent à exécuter une boucle de chargement 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 sur l'appareil lorsque l'application n'utilise pas les ressources. L'API PerformanceHint du processeur est conçue pour résoudre ce problème. Par indiquant au système la durée réelle et la durée de travail cible, Android peut obtenir un aperçu des besoins en processeur de l'application et allouer vos ressources de manière efficace. Vous obtiendrez des performances optimales avec une consommation d'énergie efficace le niveau de consommation.

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. Gouverneur peut mettre environ 200 ms à augmenter ou réduire la fréquence du processeur. L'ADF fonctionne avec le système de scaling dynamique de la tension et de la fréquence (DVFS, Dynamic Tentage and Frequency Scaling) pour offrir les meilleures performances par watt.

L'API PerformanceHint récupère davantage de latences que DVFS

ADPF extrait plus que les latences DVFS
Figure 2. L'ADF sait comment prendre les meilleures décisions à votre place.
<ph type="x-smartling-placeholder">
    </ph>
  • Si les tâches doivent être exécutées sur un processeur spécifique, l'API PerformanceHint sait comment prendre cette décision en votre nom.
  • Par conséquent, vous n'avez pas besoin d'utiliser l'affinité.
  • Les appareils ont différentes topologies : La puissance et les caractéristiques thermiques trop variés pour être exposés au développeur de l'application.
  • Vous ne pouvez pas faire d'hypothèses sur le système sous-jacent sur lequel vous exécutez.

Solution

ADPF fournit le PerformanceHintManager pour que les jeux puissent envoyer à Android des indices de performances concernant la vitesse d'horloge du processeur et un type de cœur spécifique. 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. Par exemple: <ph type="x-smartling-placeholder">
      </ph>
    • Le thread de rendu et ses dépendances obtiennent une session <ph type="x-smartling-placeholder">
        </ph>
      1. Dans Cocos, le thread du moteur principal et le thread de rendu obtiennent un cette 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 Options d'évolutivité permettant d'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 habituelle équivaut à un intervalle d'images, mais l'application peut utiliser un un intervalle plus court si la charge de travail ne varie pas de manière significative entre les frames.

Voici comment mettre la théorie en pratique:

Initialiser PerformanceHintManager et createHintSession

Obtenir le gestionnaire à l'aide du service système et créer une session d'indices pour votre thread ou un 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éfinissez des threads si nécessaire

Date de sortie :

Android 11 (niveau d'API 34)

Utilisez le setThreads fonction de PerformanceHintManager.Session lorsque vous avez d'autres threads à ajouter ultérieurement. Par exemple, si vous créez un thread de physique 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 devez détruire la session et à recréer une session chaque fois que vous devez modifier les ID de thread.

Rapport de la durée réelle de travail

Mesurez la durée réelle nécessaire pour effectuer le travail en nanosecondes et générez un rapport au système à la fin du travail à chaque cycle. Par exemple, si pour vos threads de rendu, appelez-le sur chaque frame.

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

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

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 de travail cible change, par exemple si le joueur choisit un FPS cible différente, appelez la méthode updateTargetWorkDuration pour informer le système afin que le système d'exploitation puisse ajuster les ressources en fonction à la nouvelle cible. Vous n'avez pas besoin de l'appeler sur chaque frame et vous n'avez qu'à appelez-le lorsque la durée cible change.

C++

APerformanceHint_updateTargetWorkDuration(hint_session, target_duration);

Java

hintSession.updateTargetWorkDuration(targetDuration);