Veröffentlicht:
Android 12 (API-Level 31) – Performance Hint API
Android 13 (API‑Level 33) – Performance Hint Manager in der NDK API
(Vorabversion) Android 15 (DP1) – reportActualWorkDuration()
Mit CPU-Leistungshinweisen kann ein Spiel das dynamische CPU-Leistungsverhalten beeinflussen, um es besser an seine Anforderungen anzupassen. Auf den meisten Geräten passt Android die CPU-Taktfrequenz und den Kerntyp für eine Arbeitslast dynamisch an die vorherigen Anforderungen an. Wenn für eine Arbeitslast mehr CPU-Ressourcen benötigt werden, wird die Taktfrequenz erhöht und die Arbeitslast wird schließlich auf einen größeren Kern verschoben. Wenn für die Arbeitslast weniger Ressourcen benötigt werden, reduziert Android die Ressourcenzuweisung. Mit ADPF kann die Anwendung oder das Spiel ein zusätzliches Signal zu seiner Leistung und seinen Fristen senden. So kann das System aggressiver hochgefahren werden (was die Leistung verbessert) und die Taktraten schnell gesenkt werden, wenn die Arbeitslast abgeschlossen ist (was den Stromverbrauch senkt).
Taktrate
Wenn Android-Geräte die CPU-Taktfrequenz dynamisch anpassen, kann sich die Frequenz auf die Leistung Ihres Codes auswirken. Es ist wichtig, Code zu entwickeln, der auf dynamische Taktfrequenzen reagiert, um die Leistung zu maximieren, einen sicheren thermischen Zustand aufrechtzuerhalten und Energie effizient zu nutzen. Sie können CPU-Frequenzen nicht direkt in Ihrem App-Code zuweisen. Daher versuchen Apps häufig, mit höheren CPU-Taktraten zu laufen, indem sie eine Busy-Loop in einem Hintergrundthread ausführen, damit die Arbeitslast anspruchsvoller erscheint. Das ist nicht empfehlenswert, da es Strom verschwendet und die thermische Belastung des Geräts erhöht, wenn die App die zusätzlichen Ressourcen nicht tatsächlich nutzt. Die CPU PerformanceHint
API wurde entwickelt, um dieses Problem zu beheben. Wenn Sie dem System die tatsächliche und die angestrebte Arbeitsdauer mitteilen, kann Android sich einen Überblick über den CPU-Bedarf der App verschaffen und Ressourcen effizient zuweisen. So wird eine optimale Leistung bei effizientem Stromverbrauch erzielt.
Haupttypen
Die CPU-Kern-Typen, auf denen Ihr Spiel ausgeführt wird, sind ein weiterer wichtiger Leistungsfaktor. Auf Android-Geräten wird der einem Thread zugewiesene CPU-Kern häufig dynamisch auf Grundlage des aktuellen Arbeitslastverhaltens geändert. Die Zuweisung von CPU-Kernen ist bei SoCs mit mehreren Kerntypen noch komplexer. Auf einigen dieser Geräte können die größeren Kerne nur kurz verwendet werden, ohne dass es zu einer thermisch nicht nachhaltigen Situation kommt.
Ihr Spiel sollte aus folgenden Gründen nicht versuchen, die CPU-Kernaffinität festzulegen:
- Der beste Kerntyp für eine Arbeitslast variiert je nach Gerätemodell.
- Die Nachhaltigkeit der Ausführung größerer Kerne variiert je nach SoC und den verschiedenen Kühllösungen, die von den einzelnen Gerätemodellen bereitgestellt werden.
- Die Umweltauswirkungen auf den thermischen Zustand können die Auswahl des Kerns weiter erschweren. Beispielsweise können das Wetter oder eine Smartphone-Hülle den thermischen Zustand eines Geräts verändern.
- Die Auswahl der Kerne kann nicht an neue Geräte mit zusätzlicher Leistung und zusätzlichen thermischen Funktionen angepasst werden. Daher wird die Prozessoraffinität eines Spiels oft ignoriert.
Beispiel für das Standardverhalten des Linux-Schedulers

Die PerformanceHint API abstrahiert mehr als nur DVFS-Latenzen.

- Wenn die Aufgaben auf einer bestimmten CPU ausgeführt werden müssen, kann die PerformanceHint API diese Entscheidung für Sie treffen.
- Daher müssen Sie keine Affinität verwenden.
- Geräte haben unterschiedliche Topologien. Die Leistungs- und thermischen Eigenschaften sind zu unterschiedlich, um App-Entwicklern zur Verfügung gestellt zu werden.
- Sie können keine Annahmen über das zugrunde liegende System treffen, auf dem Sie arbeiten.
Lösung
ADPF bietet die PerformanceHintManager
-Klasse, damit Spiele Leistungshinweise für die CPU-Taktfrequenz und den Core-Typ an Android senden können. Das Betriebssystem kann dann entscheiden, wie die Hinweise basierend auf dem SoC und der thermischen Lösung des Geräts am besten verwendet werden. Wenn Ihre App diese API zusammen mit der Überwachung des thermischen Zustands verwendet, kann sie dem Betriebssystem fundiertere Hinweise geben, anstatt Busy-Loops und andere Programmiertechniken zu verwenden, die zu einer Drosselung führen können.
So werden Leistungshinweise in einem Spiel verwendet:
- Hinweissitzungen für wichtige Threads erstellen, die sich ähnlich verhalten. Beispiel:
- Rendering-Thread und seine Abhängigkeiten erhalten eine Sitzung
- In Cocos erhalten der Haupt-Engine-Thread und der Render-Thread eine Sitzung.
- Binden Sie in Unity das Adaptive Performance Android Provider-Plug-in ein.
- In Unreal das Unreal Adaptive Performance-Plug-in einbinden und Skalierbarkeitsoptionen verwenden, um mehrere Qualitätsstufen zu unterstützen
- IO-Threads erhalten eine weitere Sitzung
- Audio-Threads erhalten eine dritte Sitzung
- Rendering-Thread und seine Abhängigkeiten erhalten eine Sitzung
- Das Spiel sollte dies frühzeitig tun, mindestens 2 ms und vorzugsweise mehr als 4 ms, bevor für eine Sitzung mehr Systemressourcen benötigt werden.
- Pro Hinweis-Sitzung wird die Dauer vorhergesagt, die für die Ausführung der jeweiligen Sitzung erforderlich ist. Die typische Dauer entspricht einem Frame-Intervall. Die App kann jedoch ein kürzeres Intervall verwenden, wenn die Arbeitslast zwischen den Frames nicht wesentlich variiert.
So setzen Sie die Theorie in die Praxis um:
PerformanceHintManager initialisieren und createHintSession aufrufen
Rufen Sie den Manager über den Systemdienst ab und erstellen Sie eine Hinweis-Sitzung für Ihren Thread oder Ihre Threadgruppe, die an derselben Arbeitslast arbeitet.
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);
Bei Bedarf Threads festlegen
Veröffentlicht:
Android 11 (API-Level 34)
Verwenden Sie die Funktion setThreads
des PerformanceHintManager.Session
, wenn Sie später weitere Threads hinzufügen müssen. Wenn Sie beispielsweise Ihren Physik-Thread später erstellen und ihn der Sitzung hinzufügen müssen, können Sie diese setThreads
API verwenden.
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);
Wenn Sie auf niedrigere API-Levels abzielen, müssen Sie die Sitzung beenden und jedes Mal eine neue Sitzung erstellen, wenn Sie die Thread-IDs ändern müssen.
Tatsächliche Arbeitsdauer melden
Die tatsächliche Dauer, die zum Erledigen der Aufgabe benötigt wird, wird in Nanosekunden erfasst und nach Abschluss der Aufgabe in jedem Zyklus an das System gemeldet. Wenn Sie dies beispielsweise für Ihre Rendering-Threads verwenden, rufen Sie diese Funktion bei jedem Frame auf.
Verwenden Sie Folgendes, um die tatsächliche Zeit zuverlässig abzurufen:
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();
Beispiel:
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);
Zielarbeitszeit bei Bedarf aktualisieren
Wenn sich die angestrebte Arbeitsdauer ändert, z. B. wenn der Spieler eine andere Ziel-FPS auswählt, rufen Sie die Methode updateTargetWorkDuration
auf, um das System darüber zu informieren, damit das Betriebssystem die Ressourcen entsprechend dem neuen Ziel anpassen kann. Sie müssen sie nicht für jeden Frame aufrufen, sondern nur, wenn sich die Zieldauer ändert.
C++
APerformanceHint_updateTargetWorkDuration(hint_session, target_duration);
Java
hintSession.updateTargetWorkDuration(targetDuration);