Anno di uscita:
Android 12 (livello API 31) - PerformanceHintManager
Android 13 (livello API 33) - Performance Hint Manager nell'API NDK
Android 15 (DP1) - reportActualWorkDuration()
Con i suggerimenti sul rendimento della CPU, un'app può influenzare il comportamento dinamico del rendimento della CPU per soddisfare meglio le proprie esigenze. Nella maggior parte dei dispositivi, Android regola dinamicamente la velocità di clock della CPU e il tipo di core per un carico di lavoro in base alle richieste precedenti. Se un carico di lavoro utilizza più risorse della CPU, la velocità di clock viene aumentata e il carico di lavoro viene infine spostato su un core più grande. Se il carico di lavoro utilizza meno risorse, Android riduce l'allocazione delle risorse. Con ADPF, un'app può inviare un segnale aggiuntivo sul suo rendimento e sulle scadenze. In questo modo, il sistema può aumentare più aggressivamente (migliorando il rendimento) e ridurre rapidamente i clock al termine del carico di lavoro (risparmiando il consumo di energia).
Velocità di clock
Quando i dispositivi Android regolano dinamicamente la velocità di clock della CPU, la frequenza può modificare il rendimento del codice. La progettazione di codice che gestisce le velocità di clock dinamiche è importante per massimizzare il rendimento, 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 essere eseguite a velocità di clock della CPU più elevate è eseguire un ciclo di attesa in un thread in background in modo che il carico di lavoro sembri più impegnativo. Questa è una pratica scorretta perché 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.
Comunicando al sistema la durata effettiva del lavoro e la durata target del lavoro, Android sarà in grado di ottenere una panoramica delle esigenze della CPU dell'app e allocare le risorse in modo efficiente. Ciò comporterà un rendimento ottimale a 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 di rendimento importante. 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 senza entrare in uno stato termicamente insostenibile.
L'app non deve tentare di impostare l'affinità dei core della CPU per i seguenti motivi:
- Il tipo di core migliore per un carico di lavoro 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 dei core. Ad esempio, le condizioni meteorologiche o una custodia del telefono possono modificare lo stato termico di un dispositivo.
- La selezione dei core non può ospitare nuovi dispositivi con funzionalità termiche e di rendimento aggiuntive. Di conseguenza, i dispositivi spesso ignorano l'affinità del processore di un'app.
Esempio di comportamento dello scheduler Linux predefinito
L'API PerformanceHint astrae più delle latenze DVFS
- 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 termiche e di alimentazione sono troppo varie per essere esposte allo sviluppatore di app.
- Non puoi fare ipotesi sul sistema sottostante su cui stai eseguendo l'app.
Soluzione
ADPF provides the PerformanceHintManager
classe in modo che le app possano inviare suggerimenti sul rendimento 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 cicli di attesa e altre tecniche di codifica che possono causare limitazioni.
Ecco come mettere in pratica la teoria:
Inizializzare PerformanceHintManager e createHintSession
Ottieni il gestore utilizzando il servizio di sistema e crea una sessione di suggerimenti per il thread o il gruppo di thread che lavorano 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);
Impostare i thread, se necessario
Anno di uscita:
Android 11 (livello API 34)
Utilizza la setThreads
funzione di PerformanceHintManager.Session quando hai altri thread
da aggiungere 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 utilizzi livelli API inferiori, dovrai eliminare la sessione e crearne una nuova ogni volta che devi modificare gli ID dei thread.
Segnalare la durata effettiva del lavoro
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, chiama questa funzione 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 target del lavoro, se necessario
Ogni volta che la durata target del lavoro cambia, ad esempio se il player sceglie un
FPS target diverso, chiama il updateTargetWorkDuration
metodo per comunicarlo al sistema in modo che il sistema operativo possa regolare le risorse in base
al nuovo target. Non devi chiamarlo su ogni frame, ma solo quando la durata target cambia.
C++
APerformanceHint_updateTargetWorkDuration(hint_session, target_duration);
Java
hintSession.updateTargetWorkDuration(targetDuration);