Performance Hint API

Veröffentlicht:

Android 12 (API-Level 31) – PerformanceHintManager

Android 13 (API-Level 33) – Performance Hint Manager in der NDK API

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-Taktgeschwindigkeit und den Kerntyp für eine Arbeitslast dynamisch an die vorherigen Anforderungen an. Wenn eine Arbeitslast mehr CPU-Ressourcen verwendet, wird die Taktgeschwindigkeit erhöht und die Arbeitslast schließlich auf einen größeren Kern verschoben. Wenn die Arbeitslast weniger Ressourcen verwendet, reduziert Android die Ressourcenzuweisung. Mit ADPF kann eine App ein zusätzliches Signal zu ihrer Leistung und ihren Fristen senden. So kann das System aggressiver hochgefahren werden (Leistungsverbesserung) und die Taktgeschwindigkeit schnell gesenkt werden, wenn die Arbeitslast abgeschlossen ist (Energieeinsparung).

Taktgeschwindigkeit

Wenn Android-Geräte ihre CPU-Taktgeschwindigkeit dynamisch anpassen, kann die Frequenz die Leistung Ihres Codes beeinflussen. Code zu entwickeln, der dynamische Taktgeschwindigkeiten berücksichtigt, ist wichtig, 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-Taktgeschwindigkeiten zu laufen, indem sie eine Busy-Loop in einem Hintergrundthread ausführen, damit die Arbeitslast anspruchsvoller erscheint. Das ist keine gute Vorgehensweise, da sie Energie verschwendet und die thermische Belastung des Geräts erhöht, wenn die App die zusätzlichen Ressourcen nicht tatsächlich nutzt. Die CPU-API PerformanceHint wurde entwickelt, um dieses Problem zu beheben. Wenn Sie dem System die tatsächliche und die Zielarbeitsdauer mitteilen, kann Android einen Überblick über die CPU-Anforderungen der App erhalten und Ressourcen effizient zuweisen. So wird eine optimale Leistung bei effizientem Energieverbrauch erreicht.

Kerntypen

Die CPU-Kerntypen, auf denen Ihre App ausgeführt wird, sind ein weiterer wichtiger Leistungsfaktor. Android-Geräte ändern den einem Thread zugewiesenen CPU-Kern häufig dynamisch basierend auf dem bisherigen Arbeitslastverhalten. Die Zuweisung von CPU-Kernen ist auf SoCs mit mehreren Kerntypen noch komplexer. Auf einigen dieser Geräte können die größeren Kerne nur kurz verwendet werden, ohne dass ein thermisch nicht nachhaltiger Zustand eintritt.

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 thermischen Lösungen, die von den einzelnen Gerätemodellen bereitgestellt werden.
  • Die Umweltauswirkungen auf den thermischen Zustand können die Kernauswahl weiter erschweren. Beispielsweise können das Wetter oder eine Handyhülle den thermischen Zustand eines Geräts verändern.
  • Bei der Kernauswahl können keine neuen Geräte mit zusätzlichen Leistungs- und thermischen Funktionen berücksichtigt werden. Daher ignorieren Geräte häufig die Prozessoraffinität einer App.

Beispiel für das Standardverhalten des Linux-Schedulers

Verhalten des Linux-Schedulers
Abbildung 1. Der Governor kann etwa 200 ms benötigen, um die CPU-Frequenz zu erhöhen oder zu senken. ADPF arbeitet mit dem System für dynamische Spannungs- und Frequenzskalierung (Dynamic Voltage and Frequency Scaling, DVFS) 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 die beste Entscheidung für Sie getroffen wird.
  • 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 provides the PerformanceHintManager Klasse, mit der Apps Leistungshinweise für CPU-Taktgeschwindigkeit und Kerntyp an Android senden können. Das Betriebssystem kann dann entscheiden, wie die Hinweise am besten verwendet werden, basierend auf dem SoC und der thermischen Lösung des Geräts. 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 Drosselung führen können.

So setzen Sie die Theorie in die Praxis um:

PerformanceHintManager initialisieren und Hint-Sitzung erstellen

Rufen Sie den Manager über den Systemdienst ab und erstellen Sie eine Hinweis-Sitzung für Ihren Thread oder Ihre Thread-Gruppe, 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);

Threads festlegen (falls erforderlich)

Veröffentlicht:

Android 11 (API-Level 34)

Verwenden Sie die setThreads Funktion von 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-Level 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

Erfassen Sie die tatsächliche Dauer, die zum Ausführen der Arbeit in Nanosekunden benötigt wird, und melden Sie sie nach Abschluss der Arbeit in jedem Zyklus an das System. Wenn dies beispielsweise für Ihre Rendering-Threads gilt, rufen Sie diese Funktion bei jedem Frame auf.

So erhalten Sie die tatsächliche Zeit zuverlässig:

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

Zielarbeitsdauer bei Bedarf aktualisieren

Wenn sich die Zielarbeitsdauer ändert, z. B. wenn der Player eine andere Ziel-FPS auswählt, rufen Sie die updateTargetWorkDuration Methode auf, um das System zu informieren, damit das Betriebssystem die Ressourcen entsprechend dem neuen Ziel anpassen kann. Sie müssen sie nicht bei jedem Frame aufrufen, sondern nur, wenn sich die Zieldauer ändert.

C++

APerformanceHint_updateTargetWorkDuration(hint_session, target_duration);

Java

hintSession.updateTargetWorkDuration(targetDuration);