Interfejs Performance Hint API

Data wydania:

Android 12 (poziom interfejsu API 31) – Performance Hint API

Android 13 (poziom interfejsu API 33) – Performance Hint Manager w interfejsie NDK API

(Wersja testowa) Android 15 (DP1) – reportActualWorkDuration()

Dzięki wskazówkom dotyczącym wydajności procesora gra może wpływać na dynamiczne zachowanie procesora i lepiej dopasować je do swoich potrzeb. Na większości urządzeń Android dynamicznie dostosowuje częstotliwość zegara procesora i typ rdzenia do obciążenia na podstawie poprzednich zapotrzebowań. Jeśli zadanie wykorzystuje więcej zasobów procesora, szybkość zegara się zwiększa, a obciążenie jest w końcu przenoszone do większego rdzenia. Jeśli zadanie zużywa mniej zasobów, Android zmniejsza przydział zasobów. Dzięki ADPF aplikacja lub gra może wysyłać dodatkowy sygnał na temat wydajności i terminów. Dzięki temu system będzie mógł agresywniej się rozruch (poprawić wydajność) i szybciej obniżyć zegary po zakończeniu zadań (co pozwoli zaoszczędzić zużycie energii).

Taktowanie zegara

Gdy urządzenia z Androidem dynamicznie dostosowują szybkość zegara procesora, częstotliwość ta może wpływać na wydajność kodu. Zaprojektowanie kodu zgodnego z dynamicznymi prędkościami zegara jest ważne, ponieważ pozwala zmaksymalizować wydajność, utrzymać bezpieczną temperaturę i zużywać energię. Częstotliwości procesora nie można przypisywać bezpośrednio w kodzie aplikacji. W rezultacie aplikacje, które próbują działać z większą prędkością zegara procesora, często wykonują pętlę zajętości w wątku w tle, dzięki czemu to zadanie wydaje się bardziej wymagające. Jest to niepożądana metoda, ponieważ powoduje marnowanie energii i zwiększa obciążenie cieplne urządzenia, gdy aplikacja w rzeczywistości nie zużywa dodatkowych zasobów. Interfejs API CPU PerformanceHint został zaprojektowany do rozwiązania tego problemu. Dzięki temu, że system zna rzeczywisty czas pracy i docelowy czas pracy, Android może uzyskać informacje o potrzebach aplikacji dotyczących procesora i sprawnie przydzielać zasoby. Pozwoli to uzyskać optymalną wydajność na poziomie efektywnego zużycia energii.

Typy rdzeni

Kolejnym ważnym czynnikiem wydajności są typy rdzeni procesora, na których działa gra. Urządzenia z Androidem często dynamicznie zmieniają rdzeń procesora przypisany do wątku na podstawie niedawnego zachowania zadań. Przypisanie rdzeni procesora jest jeszcze bardziej złożone w przypadku układów SOC z wieloma typami rdzeni. Na niektórych z tych urządzeń większe rdzenie można używać tylko przez krótki czas. Nie można w ten sposób doprowadzić do niezrównoważonego działania cieplnego.

Gra nie powinna próbować określić koligacji procesora z tych powodów:

  • Najlepszy typ rdzeni dla zbioru zadań różni się w zależności od modelu urządzenia.
  • Zrównoważony rozwój większych rdzeni różni się w zależności od SOC i różnych rozwiązań cieplnych dostępnych w poszczególnych modelach urządzeń.
  • Wpływ na środowisko termiczne może dodatkowo komplikować podstawowy wybór. Na przykład pogoda lub etui na telefon mogą wpływać na temperaturę urządzenia.
  • Wybór rdzeni nie może obsłużyć nowych urządzeń o większej wydajności i możliwościach cieplnych. W efekcie urządzenia często ignorują koligację procesora z grą.

Przykład domyślnego działania algorytmu szeregowania w systemie Linux

Działanie algorytmu szeregowania systemu Linux
Rysunek 1. Zwiększenie lub zmniejszenie częstotliwości procesora może potrwać ok. 200 ms. ADPF współpracuje z systemem dynamicznego skalowania napięcia i częstotliwości (DVFS), aby zapewnić najlepszą wydajność na wat

Interfejs PerformanceHint API pobiera więcej danych niż czasy oczekiwania w DVFS

ADPF zajmuje więcej czasu niż opóźnienia DVFS
Rysunek 2. ADPF wie, jak podejmować najlepszą decyzję w Twoim imieniu.
  • Jeśli zadania muszą być uruchamiane na określonym procesorze, PerformanceHint API wie, jak podjąć tę decyzję w Twoim imieniu.
  • W związku z tym nie musisz używać koligacji.
  • Urządzenia mają różne topologie: moc i temperatura są zbyt zróżnicowane, aby można było pokazać je deweloperowi aplikacji.
  • Nie możesz wyciągać żadnych założeń dotyczących używanego systemu.

Rozwiązanie

ADPF udostępnia klasę PerformanceHintManager, dzięki czemu gry mogą wysyłać do Androida wskazówki dotyczące wydajności w zależności od częstotliwości zegara procesora i typu rdzenia. System operacyjny może następnie zdecydować, jak najlepiej wykorzystać wskazówki w zależności od układu SOC i rozwiązania cieplnego urządzenia. Jeśli Twoja aplikacja korzysta z tego interfejsu API w połączeniu z monitorowaniem stanu termicznego, może przekazywać do systemu operacyjnego bardziej świadome wskazówki, zamiast korzystać z pętli zajętości i innych technik kodowania, które mogą powodować ograniczanie.

Oto jak gra korzysta ze wskazówek dotyczących wydajności:

  1. Utwórz sesje podpowiedzi dla kluczowych wątków, które zachowują się podobnie. Na przykład:
    • Wątek renderowania i jego zależności uzyskują 1 sesję.
      1. W systemie Cocos główny wątek wyszukiwarki i wątek renderowania uzyskuje jedną sesję
      2. Zintegruj w Unity wtyczkę Adaptive Performance Android Provider
      3. Zintegruj w niej wtyczkę Unreal Adaptive Performance i używaj opcji skalowalności, aby obsługiwać wiele poziomów jakości
    • Wątki zamówienia reklamowego otrzymują kolejną sesję
    • Wątki audio otrzymują trzecią sesję
  2. Gra powinna zrobić to wcześniej. Czas oczekiwania powinien wynosić co najmniej 2 ms, a najlepiej 4 ms, zanim sesja będzie wymagać zwiększenia zasobów systemowych.
  3. W każdej sesji podpowiedzi przewiduj czas trwania niezbędny do uruchomienia każdej sesji. Typowy czas trwania odpowiada interwałowi klatek, ale aplikacja może używać krótszego odstępu, jeśli zadanie nie różni się znacząco w zależności od klatek.

Oto jak zastosować teorię w praktyce:

Zainicjuj usługę PerformanceHintManager i utwórzHintSession

Pobierz menedżera za pomocą usługi systemowej i utwórz sesję podpowiedzi dla wątku lub grupy wątków pracującej na tym samym zadaniu.

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

W razie potrzeby ustaw wątki

Data wydania:

Android 11 (poziom API 34)

Jeśli masz inne wątki, które musisz dodać później, użyj funkcji setThreads PerformanceHintManager.Session. Jeśli na przykład utworzysz później wątek związany z fizyką i chcesz go dodać do sesji, możesz użyć interfejsu 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);

Jeśli kierujesz reklamy na niższe poziomy API, musisz zniszczyć sesję i utworzyć nową za każdym razem, gdy zajdzie potrzeba zmiany identyfikatorów wątków.

Zgłoś rzeczywisty czas pracy

W każdym cyklu możesz śledzić rzeczywisty czas trwania potrzebny do ukończenia zadania i zgłaszać go do systemu po jego zakończeniu. Jeśli np. dotyczy to wątków renderowania, wywołaj to przy każdej klatce.

Aby uzyskać informacje o rzeczywistym czasie, użyj polecenia:

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

Na przykład:

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

W razie potrzeby zaktualizuj docelowy czas trwania pracy

Za każdym razem, gdy docelowy czas trwania pracy ulegnie zmianie, np. jeśli gracz wybierze inną docelową liczbę klatek na sekundę, wywołaj metodę updateTargetWorkDuration, aby powiadomić system, aby system operacyjny mógł dostosować zasoby do nowego celu. Nie musisz wywoływać go przy każdej klatce i wywołujesz je tylko po zmianie docelowego czasu trwania.

C++

APerformanceHint_updateTargetWorkDuration(hint_session, target_duration);

Java

hintSession.updateTargetWorkDuration(targetDuration);