API Performance Hint

Uscita:

Android 12 (livello API 31) - API Performance Hint

Android 13 (livello API 33) - Performance Hint Manager nell'API NDK

(Anteprima) Android 15 (DP1) - reportActualWorkDuration()

Con i suggerimenti sulle prestazioni della CPU, un gioco può influenzare le prestazioni dinamiche della CPU un comportamento più adatto alle sue esigenze. Sulla maggior parte dei dispositivi, Android si regola dinamicamente la velocità di clock della CPU e il tipo di core per un carico di lavoro in base alle esigenze precedenti. Se un carico di lavoro utilizza più risorse della CPU, la velocità di clock viene aumentata e viene poi spostato in un core più grande. Se il carico di lavoro utilizza meno risorse di risorse, mentre Android riduce l'allocazione delle risorse. Con ADPF, l'applicazione o un gioco può inviare un ulteriore indicatore sul suo rendimento e sulle relative scadenze. Questo consente al sistema di crescere in modo più aggressivo (migliorando le prestazioni) e riducendo l'orologio quando il carico di lavoro è completo (risparmio energetico).

Velocità orologio

Quando i dispositivi Android regolano dinamicamente la velocità di clock della CPU, la frequenza può modificare le prestazioni del codice. Progettare un codice per gestire l'orologio dinamico velocità è importante per massimizzare le prestazioni, mantenere una temperatura e utilizzando l'energia in modo efficiente. Non puoi assegnare direttamente le frequenze CPU nel codice dell'app. Di conseguenza, un modo comune per far funzionare le app Le velocità di clock della CPU prevedono l'esecuzione di un loop di occupato in un thread in background in modo che il carico di lavoro sembra più impegnativa. Si tratta di una cattiva pratica perché spreca energia e aumenta il carico termico sul dispositivo quando l'app non sta effettivamente utilizzando la Google Cloud. L'API CPU PerformanceHint è progettata per risolvere questo problema. Di per comunicare al sistema la durata effettiva del lavoro e la durata prevista Android potrà ottenere una panoramica delle esigenze di CPU dell'app e allocare risorse in modo efficiente. Ciò porterà a prestazioni ottimali con un'alimentazione efficiente e il livello di consumo eccessivo.

Tipi di core

I tipi di core della CPU su cui viene eseguito il gioco sono un'altra importante prestazione fattore. I dispositivi Android spesso cambiano il core della CPU assegnato a un thread dinamicamente in base al comportamento recente del carico di lavoro. L'assegnazione dei core della CPU è ancora più complessi su SoC con più tipi di core. Su alcuni di questi dispositivi, la maggiore I core possono essere utilizzati solo per breve tempo senza entrare in un ambiente termicamente insostenibile stato.

Il tuo gioco non dovrebbe provare a impostare l'affinità dei core della CPU per i seguenti motivi:

  • Il miglior tipo di core per un carico di lavoro varia in base al modello di dispositivo.
  • La sostenibilità dell'esecuzione di core più grandi varia in base al SoC e ai vari soluzioni termiche fornite da ciascun modello di dispositivo.
  • L'impatto ambientale sullo stato termico può complicare ulteriormente il nucleo scelta. Ad esempio, il meteo o la custodia dello smartphone possono cambiare lo stato termico di un dispositivo.
  • La selezione dei componenti principali non è in grado di supportare i nuovi dispositivi con prestazioni e prestazioni aggiuntive termiche. Di conseguenza, i dispositivi spesso ignorano il processore di un gioco di affinità.

Esempio di comportamento predefinito dello scheduler di Linux

Comportamento Linux Scheduler
Figura 1. Il governatore può impiegare circa 200 ms per aumentare o diminuire la frequenza della CPU. La tecnologia ADPF funziona con il sistema Dynamic Voltage and frequenza Scaling (DVFS) per offrire le migliori prestazioni per watt.
.

L'API PerformanceHint astrae più delle latenze di DVFS

L'APF elabora più informazioni delle latenze DVFS
Figura 2. L'ADPF sa come prendere la decisione migliore per tuo conto
.
    .
  • Se le attività devono essere eseguite su una CPU specifica, l'API PerformanceHint sa come eseguire a prendere questa decisione per tuo conto.
  • Pertanto, non è necessario utilizzare l'affinità.
  • I dispositivi hanno varie topologie: Le caratteristiche di potenza e temperatura sono troppo vari per essere esposti agli sviluppatori di app.
  • Non puoi fare ipotesi sul sistema sottostante su cui stai eseguendo.

Soluzione

L'ADF fornisce la classe PerformanceHintManager in modo che i giochi possano inviare suggerimenti sulle prestazioni ad Android per la velocità di clock della CPU e tipo di core. Il sistema operativo può quindi decidere come utilizzare al meglio i suggerimenti in base al SoC e soluzione termica del dispositivo. Se la tua app usa questa API insieme a termiche monitoraggio dello stato, può fornire suggerimenti più consapevoli al sistema operativo anziché utilizzare loop di traffico e altre tecniche di programmazione che possono causare la limitazione.

Ecco come un gioco utilizza i suggerimenti per le prestazioni:

  1. Crea sessioni di suggerimento per i thread chiave con un comportamento simile. Ad esempio:
    • Il thread di rendering e le sue dipendenze richiedono una sessione
      1. In Cocos, il thread del motore principale e il thread di rendering hanno uno sessione
      2. In Unity, integra il plug-in del provider Android con prestazioni adattive
      3. In Unreal, integra il plug-in Unreal Adaptive Performance e utilizza Opzioni di scalabilità per supportare più livelli qualitativi
    • I thread di I/O ricevono un'altra sessione
    • I thread audio ricevono una terza sessione
  2. Il gioco dovrebbe farlo presto, almeno 2 ms e preferibilmente più di 4 ms prima che una sessione richieda maggiori risorse di sistema.
  3. In ogni sessione di suggerimento, prevedi la durata necessaria per l'esecuzione di ogni sessione. La durata tipica equivale a un intervallo di frame, ma l'app può utilizzare una se il carico di lavoro non varia in modo significativo da un frame all'altro.

Ecco come mettere in pratica la teoria:

inizializza PerformanceHintManager e createHintSession

Fai utilizzare il gestore al servizio di sistema e crea una sessione hint per il tuo thread o un gruppo di thread che lavora sullo stesso carico di lavoro.

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

Imposta i thread, se necessario

Uscita:

Android 11 (livello API 34)

Utilizza la setThreads funzione di PerformanceHintManager.Session quando ci sono altri thread che devono essere aggiunti in un secondo momento. Ad esempio, se crei il tuo thread relativo alla fisica in un secondo momento e devi aggiungerlo alla sessione, puoi usare questa 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);

Se hai come target livelli API più bassi, dovrai eliminare la sessione e ricreano una nuova sessione ogni volta che devi modificare gli ID thread.

Report durata lavoro effettiva

Monitora la durata effettiva necessaria per completare il lavoro in nanosecondi e genera un report al sistema al termine del lavoro in ogni ciclo. Ad esempio, se per i thread di rendering, chiamala per ogni frame.

Per ottenere in modo affidabile l'ora effettiva, utilizza:

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

Ad esempio:

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

Aggiorna la durata di lavoro target quando necessario

Ogni volta che la durata di lavoro target cambia, ad esempio se il giocatore sceglie una f/s target diverso, chiama updateTargetWorkDuration per comunicare al sistema in modo che quest'ultimo possa regolare le risorse in base al nuovo target. Non è necessario chiamarla a ogni frame e basta e viene chiamato quando cambia la durata target.

C++

APerformanceHint_updateTargetWorkDuration(hint_session, target_duration);

Java

hintSession.updateTargetWorkDuration(targetDuration);