Performance Hint API

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 eine App das dynamische CPU-Leistungsverhalten beeinflussen, um es besser an ihre Anforderungen anzupassen. Auf den meisten Geräten passt Android die CPU-Taktfrequenz und den Kerntyp für eine Arbeitslast basierend auf den vorherigen Anforderungen dynamisch 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 die Arbeitslast weniger Ressourcen benötigt, wird die Ressourcenzuweisung von Android reduziert. Mit ADPF kann eine App ein zusätzliches Signal zu ihrer Leistung und ihren 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 die CPU-Anforderungen der App besser einschätzen und Ressourcen effizient zuweisen. So wird eine optimale Leistung bei effizientem Stromverbrauch erzielt.

Haupttypen

Die CPU-Kern-Typen, auf denen Ihre App 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, bevor sie sich zu stark erhitzen.

Ihre App 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 einer App von Geräten oft ignoriert.

Beispiel für das Standardverhalten des Linux-Schedulers

Verhalten des Linux-Schedulers
Abbildung 1: Es kann etwa 200 ms dauern, bis die CPU-Frequenz durch den Governor erhöht oder verringert wird. ADPF arbeitet mit dem DVFS-System (Dynamic Voltage and Frequency Scaling) zusammen, um die beste Leistung pro Watt zu erzielen.

Die PerformanceHint API abstrahiert mehr als nur DVFS-Latenzen.

ADPF abstrahiert mehr als DVFS-Latenzen
Abbildung 2. ADPF weiß, wie es in Ihrem Namen die beste Entscheidung treffen kann.
  • 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 sie App-Entwicklern zur Verfügung zu stellen.
  • Sie können keine Annahmen über das zugrunde liegende System treffen, auf dem Sie arbeiten.

Lösung

ADPF bietet die Klasse PerformanceHintManager, damit Apps 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 Codierungstechniken zu verwenden, die zu einer Drosselung führen können.

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 diese Funktion beispielsweise für Ihre Rendering-Threads verwenden, rufen Sie sie 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);