API Performance Hint

Date de sortie :

Android 12 (niveau d'API 31) – PerformanceHintManager

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

Android 15 (DP1) – reportActualWorkDuration()

Grâce aux indices de performances du processeur, une application peut influencer le comportement dynamique des performances du processeur afin de 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 besoins antérieurs. Si une charge de travail utilise davantage de ressources de processeur, la vitesse d'horloge est augmentée, et la charge de travail finit par être transférée vers un cœur de plus grande taille. Si la charge de travail utilise moins de ressources, Android diminue l'allocation des ressources. Avec ADPF, une application peut envoyer un signal supplémentaire concernant ses performances et ses délais. Cela permet au système d'accélérer plus rapidement (amélioration des performances) et de réduire rapidement les horloges 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. Il est important de concevoir du code qui s'adapte aux vitesses d'horloge dynamiques afin de maximiser les performances, de maintenir un état thermique sûr et d'optimiser la consommation d'énergie. 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 de processeur plus élevées consiste à exécuter une boucle de disponibilité dans un thread d'arrière-plan afin que la charge de travail semble plus exigeante. Il s'agit d'une mauvaise pratique, car elle gaspille de l'énergie et augmente la charge thermique de l'appareil lorsque l'application n'utilise pas réellement les ressources supplémentaires. L'API PerformanceHint du processeur est conçue pour résoudre ce problème. En informant le système de la durée de travail réelle et de la durée de travail cible, Android pourra obtenir une vue d'ensemble des besoins de l'application en termes de processeur et allouer efficacement les ressources. Cela permettra d'obtenir des performances optimales avec une consommation d'énergie efficace.

Types de cœurs

Les types de cœurs de processeur sur lesquels votre application 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 application 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'une application.

Exemple de comportement par défaut du planificateur Linux

Comportement du planificateur Linux
Figure 1. Le gouverneur peut mettre environ 200 ms pour augmenter ou diminuer la fréquence du processeur. ADPF fonctionne avec le système de mise à l'échelle dynamique de la tension et de la fréquence (DVFS, Dynamic Voltage and Frequency Scaling) pour offrir les meilleures performances par watt

L'API PerformanceHint abstrait plus que les latences DVFS

ADPF Abstracts more than DVFS Latencies
Figure 2. ADPF sait comment prendre la meilleure décision en votre nom.
  • Si les tâches doivent s'exécuter 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 sont fournis avec 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 faire aucune hypothèse sur le système sous-jacent sur lequel vous vous exécutez.

Solution

ADPF provides the PerformanceHintManager classe afin que les applications 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 passer de la théorie à la 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 les threads si nécessaire

Date de sortie :

Android 11 (niveau d'API 34)

Utilisez la setThreads fonction du PerformanceHintManager.Session lorsque vous avez d'autres threads à ajouter 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 de travail réelle

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

Pour obtenir le temps réel 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();

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

Mettre à 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 une autre fréquence d'images cible, appelez la updateTargetWorkDuration méthode pour en informer le système afin que l'OS puisse ajuster les ressources en fonction de la nouvelle cible. Vous n'avez pas besoin de l'appeler sur chaque frame. Vous ne devez l'appeler que lorsque la durée cible change.

C++

APerformanceHint_updateTargetWorkDuration(hint_session, target_duration);

Java

hintSession.updateTargetWorkDuration(targetDuration);