Interfejs Performance Hint API

Wydane:

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

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

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

Dzięki wskazówkom dotyczącym wydajności procesora aplikacja może wpływać na dynamiczną wydajność procesora, aby lepiej dopasować ją do swoich potrzeb. Na większości urządzeń Android dynamicznie dostosowuje szybkość zegara procesora i typ rdzenia do zadań na podstawie poprzednich wymagań. Jeśli zadanie wykorzystuje więcej zasobów procesora, zwiększa się szybkość zegara i zadanie jest ostatecznie przenoszone na większy rdzeń. Jeśli zadanie wykorzystuje mniej zasobów, Android zmniejsza ich przydział. Dzięki ADPF aplikacja może wysyłać dodatkowy sygnał dotyczący jej wydajności i terminów. Dzięki temu system może szybciej zwiększać taktowanie (co poprawia wydajność) i szybciej je obniżać po zakończeniu pracy (co zmniejsza zużycie energii).

Taktowanie

Gdy urządzenia z Androidem dynamicznie dostosowują częstotliwość zegara procesora, może to wpłynąć na wydajność kodu. Projektowanie kodu, który uwzględnia dynamiczne prędkości zegara, jest ważne dla maksymalizacji wydajności, utrzymania bezpiecznego stanu termicznego i efektywnego wykorzystania energii. Nie możesz bezpośrednio przypisywać częstotliwości procesora w kodzie aplikacji. Dlatego aplikacje często próbują działać z wyższą częstotliwością zegara procesora, uruchamiając pętlę w wątku w tle, aby obciążenie wydawało się większe. Jest to zła praktyka, ponieważ marnuje energię i zwiększa obciążenie termiczne urządzenia, gdy aplikacja w rzeczywistości nie korzysta z dodatkowych zasobów. Interfejs API PerformanceHint CPU został zaprojektowany, aby rozwiązać ten problem. Dzięki informacjom o rzeczywistym i docelowym czasie trwania zadania system Android będzie mógł uzyskać przegląd potrzeb aplikacji w zakresie procesora i efektywnie przydzielać zasoby. Zapewni to optymalną wydajność przy efektywnym zużyciu energii.

Typy rdzeni

Rodzaje rdzeni procesora, na których działa aplikacja, to kolejny ważny czynnik wpływający na wydajność. Urządzenia z Androidem często dynamicznie zmieniają rdzeń procesora przypisany do wątku na podstawie niedawnego zachowania zadania. Przypisywanie 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 mogą być używane tylko przez krótki czas, zanim osiągną stan, w którym temperatura będzie zbyt wysoka.

Aplikacja nie powinna próbować ustawiać powiązania z rdzeniem procesora z tych powodów:

  • Najlepszy typ rdzenia dla danego zadania różni się w zależności od modelu urządzenia.
  • Zrównoważone działanie większych rdzeni zależy od układu SoC i różnych rozwiązań termicznych stosowanych w poszczególnych modelach urządzeń.
  • Wpływ środowiska na stan termiczny może dodatkowo skomplikować wybór rdzenia. Na przykład pogoda lub etui na telefon mogą zmienić stan termiczny urządzenia.
  • Podstawowy wybór nie obejmuje nowych urządzeń o większej wydajności i lepszych możliwościach termicznych. W rezultacie urządzenia często ignorują powiązanie aplikacji z procesorem.

Przykład domyślnego działania harmonogramu systemu Linux

Działanie harmonogramu Linuksa
Rysunek 1. Zmiana częstotliwości procesora przez regulator może potrwać około 200 ms. ADPF współpracuje z systemem Dynamic Voltage and Frequency Scaling (DVFS), aby zapewnić najlepszą wydajność na wat
.

Interfejs PerformanceHint API obejmuje więcej niż tylko opóźnienia DVFS

ADPF jest bardziej abstrakcyjny niż opóźnienia DVFS
Rysunek 2. ADPF wie, jak podjąć najlepszą decyzję w Twoim imieniu.
  • Jeśli zadania muszą być wykonywane na określonym procesorze, interfejs PerformanceHint API wie, jak podjąć tę decyzję w Twoim imieniu.
  • Dlatego nie musisz używać podobieństwa.
  • Urządzenia mają różne topologie. Charakterystyka zasilania i termiczna jest zbyt zróżnicowana, aby można było ją udostępnić deweloperowi aplikacji.
  • Nie możesz zakładać niczego na temat systemu, w którym działasz.

Rozwiązanie

ADPF udostępnia klasę PerformanceHintManager, dzięki której aplikacje mogą wysyłać do Androida wskazówki dotyczące wydajności, takie jak szybkość zegara procesora i typ rdzenia. System operacyjny może wtedy zdecydować, jak najlepiej wykorzystać wskazówki na podstawie układu SoC i rozwiązania termicznego urządzenia. Jeśli Twoja aplikacja korzysta z tego interfejsu API wraz z monitorowaniem stanu termicznego, może przekazywać systemowi operacyjnemu bardziej precyzyjne wskazówki zamiast używać pętli zajętości i innych technik kodowania, które mogą powodować ograniczanie przepustowości.

Aby przełożyć teorię na praktykę:

Inicjowanie interfejsu PerformanceHintManager i tworzenie sesji wskazówek

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

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.

Wydane:

Android 11 (poziom 34 interfejsu API)

Użyj funkcji setThreadsPerformanceHintManager.Session, gdy masz inne wątki, które trzeba będzie dodać później. Jeśli na przykład utworzysz wątek fizyki później i musisz dodać go do sesji, możesz użyć tego setThreadsinterfejsu API.

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 interfejsu API, musisz niszczyć sesję i tworzyć nową za każdym razem, gdy chcesz zmienić identyfikatory wątków.

Raportowanie rzeczywistego czasu pracy

Śledź rzeczywisty czas potrzebny na wykonanie pracy w nanosekundach i zgłaszaj go do systemu po zakończeniu pracy w każdym cyklu. Jeśli na przykład dotyczy to wątków renderowania, wywołuj tę funkcję w każdej klatce.

Aby uzyskać wiarygodny czas rzeczywisty, użyj:

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 pracy

Za każdym razem, gdy zmieni się docelowy czas pracy, np. gdy gracz wybierze inny docelowy FPS, wywołaj metodę updateTargetWorkDuration, aby poinformować system, że system operacyjny może dostosować zasoby do nowego celu. Nie musisz wywoływać tej funkcji w każdej klatce. Wystarczy, że zrobisz to, gdy zmieni się docelowy czas trwania.

C++

APerformanceHint_updateTargetWorkDuration(hint_session, target_duration);

Java

hintSession.updateTargetWorkDuration(targetDuration);