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 la couche supplémentaire
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
L'API PerformanceHint récupère davantage de latences que DVFS
<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 :
- 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>
- Dans Cocos, le thread du moteur principal et le thread de rendu obtiennent un cette session
- Dans Unity, intégrez le plug-in Adaptive Performance Android Provider
- 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.
- Le thread de rendu et ses dépendances obtiennent une session
<ph type="x-smartling-placeholder">
- 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 typique é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
Déterminez en nanosecondes la durée réelle nécessaire pour effectuer le travail 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);