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 ein Spiel das dynamische CPU-Leistungsverhalten beeinflussen, um seinen Anforderungen besser zu entsprechen. Auf den meisten Geräten passt Android die CPU-Taktgeschwindigkeit und den Kerntyp basierend auf den vorherigen Anforderungen dynamisch an eine Arbeitslast an. Wenn eine Arbeitslast mehr CPU-Ressourcen verwendet, wird die Taktgeschwindigkeit erhöht und die Arbeitslast wird schließlich auf einen größeren Kern verschoben. Wenn die Arbeitslast weniger Ressourcen verbraucht, senkt Android die Ressourcenzuweisung. Mit ADPF kann die App oder das Spiel ein zusätzliches Signal zu Leistung und Fristen senden. Dies trägt dazu bei, dass das System aggressiver anläuft (verbessert die Leistung) und die Taschen schnell wieder herunterfahren, wenn die Arbeitslast abgeschlossen ist (was Energienutzung spart.)

Taktgeschwindigkeit

Wenn Android-Geräte ihre CPU-Taktgeschwindigkeit dynamisch anpassen, kann sich die Frequenz auf die Leistung des Codes auswirken. Das Entwerfen von Code für dynamische Taktgeschwindigkeiten ist wichtig für die Leistungsmaximierung, die Aufrechterhaltung eines sicheren Temperaturzustands und eine effiziente Energienutzung. Sie können CPU-Frequenzen nicht direkt im Anwendungscode zuweisen. Anwendungen, die mit einer höheren CPU-Taktgeschwindigkeit ausgeführt werden, verwenden daher häufig eine ausgelastete Schleife in einem Hintergrundthread, sodass die Arbeitslast anspruchsvoller erscheint. Dies ist nicht empfehlenswert, da dadurch Strom verschwendet und die thermische Belastung des Geräts erhöht wird, wenn die App die zusätzlichen Ressourcen nicht tatsächlich nutzt. Die CPU PerformanceHint API wurde entwickelt, um dieses Problem zu beheben. Wenn das System die tatsächliche Arbeitsdauer und die angestrebte Arbeitsdauer kennt, kann sich Android einen Überblick über die CPU-Anforderungen der App verschaffen und Ressourcen effizient zuweisen. Dies führt zu einer optimalen Leistung bei einem effizienten Stromverbrauch.

Haupttypen

Die CPU-Kerntypen, auf denen Ihr Spiel ausgeführt wird, sind ein weiterer wichtiger Leistungsfaktor. Android-Geräte ändern den einem Thread zugewiesenen CPU-Kern häufig dynamisch anhand des aktuellen Arbeitslastverhaltens. Die Zuweisung von CPU-Kernen ist auf SoCs mit mehreren Kerntypen sogar noch komplexer. Auf einigen dieser Geräte können die größeren Kerne nur kurz verwendet werden, ohne dass ein thermisch unhaltbarer Zustand erreicht wird.

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 Thermallösungen, die von den einzelnen Gerätemodellen bereitgestellt werden.
  • Die Umweltauswirkungen auf den Temperaturzustand können die Wahl des Kerns erschweren. Beispielsweise kann sich der thermische Zustand eines Geräts durch das Wetter oder eine Smartphone-Schutzhülle verändern.
  • Bei der Auswahl des Kerns sind keine neuen Geräte mit zusätzlicher Leistung und höherer Leistung möglich. Daher ignorieren Geräte oft die Prozessoraffinität eines Spiels.

Beispiel für das Standardverhalten des Linux-Planers

Verhalten des Linux-Planers
Abbildung 1: Es kann etwa 200 ms dauern, bis der Governor die CPU-Frequenz erhöht oder verringert. ADPF bietet zusammen mit dem Dynamic Voltage and Frequency Scaling System (DVFS) die beste Leistung pro Watt

PerformanceHint API abstrahiert mehr als DVFS-Latenzen

ADPF-Zusammenfassungen: mehr als DVFS-Latenzen
Abbildung 2: Das ADPF-Team weiß, wie es für Sie die beste Entscheidung trifft.
  • 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 Affinität nicht verwenden.
  • Geräte haben verschiedene Topologien. Leistungs- und thermische Eigenschaften sind zu vielfältig, um sie dem App-Entwickler zugänglich zu machen.
  • Sie können keine Annahmen über das zugrunde liegende System anstellen, auf dem Sie ausgeführt werden.

Die Lösung

ADPF bietet die Klasse PerformanceHintManager, damit Spiele zur CPU-Taktgeschwindigkeit und zum Kerntyp Leistungshinweise an Android senden können. Das Betriebssystem kann dann entscheiden, wie die Hinweise am besten basierend auf dem SoC und der Thermallösung des Geräts verwendet werden sollen. Wenn Ihre Anwendung diese API zusammen mit der Überwachung des thermischen Zustands verwendet, kann sie dem Betriebssystem besser informierte Hinweise geben, anstatt belegte Schleifen und andere Codierungstechniken zu verwenden, die eine Drosselung verursachen können.

So nutzt ein Spiel Leistungshinweise:

  1. Erstellen Sie Hinweissitzungen für wichtige Threads, die sich ähnlich verhalten. Beispiel:
    • Der Renderingthread und seine Abhängigkeiten führt zu einer Sitzung.
      1. In Cocos erhält der Haupt-Engine- und der Rendering-Thread eine Sitzung.
      2. Binden Sie in Unity das Android Provider-Plug-in „Adaptive Performance“ ein.
      3. Binden Sie das Unreal Adaptive Performance-Plug-in in Unreal ein und nutzen Sie Skalierbarkeitsoptionen, um mehrere Qualitätsstufen zu unterstützen.
    • E/A-Threads erhalten eine weitere Sitzung
    • Audiothreads erhalten eine dritte Sitzung
  2. Das Spiel sollte dies früh tun, mindestens 2 ms und vorzugsweise mehr als 4 ms, bevor eine Sitzung mehr Systemressourcen benötigt.
  3. Sagen Sie in jeder Hinweissitzung die Dauer, die für die jeweilige Sitzung benötigt wird. Die typische Dauer entspricht einem Frameintervall. Die Anwendung kann jedoch ein kürzeres Intervall verwenden, wenn die Arbeitslast nicht signifikant zwischen den Frames variiert.

So wird die Theorie in die Praxis umgesetzt:

„PerformanceHintManager“ und „createHintSession“ initialisieren

Rufen Sie den Manager mithilfe des Systemdienstes ab und erstellen Sie eine Hinweissitzung für den Thread oder die Threadgruppe, die mit derselben Arbeitslast ausgeführt wird.

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

Legen Sie bei Bedarf Threads fest.

Veröffentlicht:

Android 11 (API-Level 34)

Verwenden Sie die Funktion setThreads von PerformanceHintManager.Session, wenn Sie weitere Threads haben, die später hinzugefügt werden müssen. Wenn Sie beispielsweise Ihren Physik-Thread später erstellen und 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);

Bei niedrigeren API-Ebenen müssen Sie die Sitzung löschen und jedes Mal, wenn Sie die Thread-IDs ändern, eine neue Sitzung erstellen.

Tatsächliche Arbeitsdauer melden

Verfolgen Sie die tatsächliche Dauer, die für die Arbeit in Nanosekunden benötigt wird, und melden Sie sie nach Abschluss der Arbeit in jedem Zyklus dem System. Wenn dies beispielsweise für Ihre Rendering-Threads gilt, rufen Sie sie für jeden Frame auf.

Um die tatsächliche Zeit zuverlässig zu erhalten, verwenden Sie:

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

Beispiele:

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 angestrebte Arbeitsdauer ändert, z. B. wenn der Spieler eine andere Ziel-fps wählt, rufen Sie die Methode updateTargetWorkDuration auf, um das System zu informieren, damit das Betriebssystem die Ressourcen an das neue Ziel anpassen kann. Sie müssen es nicht bei jedem Frame aufrufen, sondern nur dann, wenn sich die Zieldauer ändert.

C++

APerformanceHint_updateTargetWorkDuration(hint_session, target_duration);

Java

hintSession.updateTargetWorkDuration(targetDuration);