API Performance Hint

Data di 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'app può influenzare il comportamento dinamico delle prestazioni della CPU per soddisfare meglio le proprie esigenze. Sulla maggior parte dei dispositivi, Android regola dinamicamente la velocità di clock della CPU e il tipo di core per un workload in base alle richieste precedenti. Se un workload utilizza più risorse CPU, la velocità di clock viene aumentata e il workload viene infine spostato su un core più grande. Se il workload utilizza meno risorse, Android riduce l'allocazione delle risorse. Con ADPF, un'app può inviare un indicatore aggiuntivo sul suo rendimento e sulle scadenze. Ciò consente al sistema di aumentare la velocità in modo più aggressivo (migliorando le prestazioni) e di abbassare rapidamente i clock al termine del carico di lavoro (risparmiando energia).

Velocità di clock

Quando i dispositivi Android regolano dinamicamente la velocità di clock della CPU, la frequenza può modificare le prestazioni del codice. Progettare un codice che gestisca le velocità di clock dinamiche è importante per massimizzare le prestazioni, mantenere uno stato termico sicuro e utilizzare l'energia in modo efficiente. Non puoi assegnare direttamente le frequenze della CPU nel codice dell'app. Di conseguenza, un modo comune per le app di tentare di funzionare a velocità di clock della CPU più elevate è eseguire un ciclo occupato in un thread in background in modo che il carico di lavoro sembri più impegnativo. Si tratta di una pratica errata, in quanto spreca energia e aumenta il carico termico sul dispositivo quando l'app non utilizza effettivamente le risorse aggiuntive. L'API PerformanceHint della CPU è progettata per risolvere questo problema. Se il sistema conosce la durata effettiva del lavoro e la durata del lavoro target, Android sarà in grado di ottenere una panoramica delle esigenze di CPU dell'app e allocare le risorse in modo efficiente. In questo modo, le prestazioni saranno ottimali con un livello di consumo energetico efficiente.

Tipi di core

I tipi di core della CPU su cui viene eseguita l'app sono un altro fattore importante per le prestazioni. I dispositivi Android spesso modificano dinamicamente il core della CPU assegnato a un thread in base al comportamento recente del carico di lavoro. L'assegnazione dei core della CPU è ancora più complessa sui SoC con più tipi di core. Su alcuni di questi dispositivi, i core più grandi possono essere utilizzati solo brevemente prima di entrare in uno stato termicamente insostenibile.

La tua app non deve tentare di impostare l'affinità dei core della CPU per i seguenti motivi:

  • Il tipo di core migliore per un workload varia in base al modello del dispositivo.
  • La sostenibilità dell'esecuzione di core più grandi varia in base al SoC e alle varie soluzioni termiche fornite da ogni modello di dispositivo.
  • L'impatto ambientale sullo stato termico può complicare ulteriormente la scelta del nucleo. Ad esempio, il meteo o una cover per smartphone possono modificare lo stato termico di un dispositivo.
  • La selezione dei core non può ospitare nuovi dispositivi con prestazioni e capacità termiche aggiuntive. Di conseguenza, i dispositivi spesso ignorano l'affinità del processore di un'app.

Esempio di comportamento predefinito dello scheduler Linux

Comportamento dello scheduler Linux
Figura 1. Il governor può impiegare circa 200 ms per aumentare o diminuire la frequenza della CPU. ADPF funziona con il sistema di scalabilità dinamica di tensione e frequenza (DVFS) per fornire le migliori prestazioni per watt

L'API PerformanceHint astrae più delle latenze DVFS

ADPF estrae più di latenze DVFS
Figura 2. 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 prendere questa decisione per tuo conto.
  • Pertanto, non è necessario utilizzare l'affinità.
  • I dispositivi sono dotati di varie topologie; le caratteristiche di alimentazione e termiche sono troppo varie per essere esposte allo sviluppatore di app.
  • Non puoi fare ipotesi sul sistema sottostante su cui stai lavorando.

Soluzione

ADPF fornisce la classe PerformanceHintManager, in modo che le app possano inviare suggerimenti sulle prestazioni ad Android per la velocità di clock della CPU e il tipo di core. Il sistema operativo può quindi decidere come utilizzare al meglio i suggerimenti in base al SoC e alla soluzione termica del dispositivo. Se la tua app utilizza questa API insieme al monitoraggio dello stato termico, può fornire suggerimenti più informati al sistema operativo anziché utilizzare loop occupati e altre tecniche di codifica che possono causare la limitazione.

Ecco come mettere in pratica la teoria:

Inizializza PerformanceHintManager e creaHintSession

Ottieni il gestore utilizzando il servizio di sistema e crea una sessione di suggerimenti per il thread o il gruppo di thread che lavora sullo stesso workload.

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

Data di uscita:

Android 11 (livello API 34)

Utilizza la funzione setThreads di PerformanceHintManager.Session quando hai altri thread che devono essere aggiunti in un secondo momento. Ad esempio, se crei il thread di fisica in un secondo momento e devi aggiungerlo alla sessione, puoi utilizzare 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 scegli come target livelli API inferiori, devi distruggere la sessione e ricreare una nuova sessione ogni volta che devi modificare gli ID thread.

Report Actual Work Duration

Tieni traccia della durata effettiva necessaria per completare il lavoro in nanosecondi e segnalala al sistema al termine del lavoro in ogni ciclo. Ad esempio, se si tratta dei thread di rendering, chiamalo su ogni frame.

Per ottenere l'ora effettiva in modo affidabile, 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);

Aggiornare la durata del lavoro target quando necessario

Ogni volta che la durata del lavoro target cambia, ad esempio se il giocatore sceglie un fps target diverso, chiama il metodo updateTargetWorkDuration per comunicare al sistema in modo che il sistema operativo possa regolare le risorse in base al nuovo target. Non devi chiamarlo a ogni frame e devi chiamarlo solo quando la durata target cambia.

C++

APerformanceHint_updateTargetWorkDuration(hint_session, target_duration);

Java

hintSession.updateTargetWorkDuration(targetDuration);