Sortie :
Android 12 (niveau d'API 31) : API Performance Hint
Android 13 (niveau d'API 33) : Gestionnaire d'indices de performances dans l'API NDK
(Preview) Android 15 (DP1) : reportActualWorkDuration()
Grâce aux conseils sur les 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 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, l'application ou le jeu peuvent envoyer un signal supplémentaire concernant leurs performances et leurs délais. Cela permet au système d'augmenter la fréquence de manière plus agressive (amélioration des performances) et de la réduire rapidement une fois la charge de travail terminée (économie d'énergie).
Vitesse d'horloge
Lorsque les appareils Android ajustent dynamiquement la vitesse d'horloge du 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 indiquant au système la durée de travail réelle et la durée de travail cible, Android pourra obtenir une vue d'ensemble des besoins en processeur de l'application et allouer les ressources de manière efficace. Cela permettra d'obtenir des performances optimales avec 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 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 à votre place.
- Vous n'avez donc pas besoin d'utiliser l'affinité.
- Les appareils sont fournis avec différentes topologies. Les caractéristiques thermiques et d'alimentation sont trop variées pour être exposées aux développeurs d'applications.
- Vous ne pouvez faire aucune hypothèse sur le système sous-jacent sur lequel vous exécutez votre code.
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 :
- Créez des sessions d'indices pour les threads clés qui se comportent de la même manière. Par exemple :
- Le thread de rendu et ses dépendances se voient attribuer une session.
- Dans Cocos, le thread du moteur principal et le thread de rendu obtiennent une session.
- Dans Unity, intégrez le plug-in Adaptive Performance Android Provider.
- Dans Unreal, intégrez le plug-in Unreal Adaptive Performance et utilisez les options de scalabilité pour prendre en charge 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.
- Le thread de rendu et ses dépendances se voient attribuer une session.
- 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.
- Prévoyez la durée nécessaire pour exécuter chaque session pour chaque session d'indices. La durée habituelle équivaut à 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 entre les frames.
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'indication 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éfinissez les threads si nécessaire.
Sortie :
Android 11 (niveau d'API 34)
Utilisez la fonction setThreads
de PerformanceHintManager.Session
lorsque vous avez d'autres threads à ajouter ultérieurement. Par exemple, si vous créez votre thread physique plus tard 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 du travail
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 c'est pour vos threads de rendu, appelez-le à chaque frame.
Pour obtenir l'heure exacte 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 lecteur choisit une autre fréquence d'images cible, appelez la méthode updateTargetWorkDuration
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. Il vous suffit de l'appeler lorsque la durée cible change.
C++
APerformanceHint_updateTargetWorkDuration(hint_session, target_duration);
Java
hintSession.updateTargetWorkDuration(targetDuration);