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
L'API PerformanceHint abstrait plus que les latences DVFS
- 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);