Interfejs Performance Hint API

Data premiery:

Android 12 (poziom 31 interfejsu API) – PerformanceHintManager

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

Android 15 (DP1) – reportActualWorkDuration()

Dzięki wskazówkom dotyczącym wydajności procesora aplikacja może wpływać na dynamiczne zachowanie procesora, aby lepiej dopasować je do swoich potrzeb. Na większości urządzeń Android dynamicznie dostosowuje taktowanie procesora i typ rdzenia do obciążenia na podstawie wcześniejszych wymagań. Jeśli obciążenie wykorzystuje więcej zasobów procesora, taktowanie procesora jest zwiększane, a obciążenie jest ostatecznie przenoszone na większy rdzeń. Jeśli obciążenie wykorzystuje mniej zasobów, Android zmniejsza przydział zasobów. Dzięki ADPF aplikacja może wysyłać dodatkowy sygnał o swojej wydajności i terminach. Pomaga to systemowi szybciej zwiększać wydajność (co ją poprawia) i szybko zmniejszać taktowanie procesora po zakończeniu obciążenia (co oszczędza energię).

Taktowanie procesora

Gdy urządzenia z Androidem dynamicznie dostosowują taktowanie procesora, częstotliwość może wpływać na wydajność kodu. Projektowanie kodu, który uwzględnia dynamiczne taktowanie procesora, jest ważne, aby zmaksymalizować wydajność, utrzymać bezpieczny stan termiczny i efektywnie wykorzystywać energię. Nie możesz bezpośrednio przypisywać częstotliwości procesora w kodzie aplikacji. Dlatego aplikacje często próbują działać z wyższym taktowaniem procesora, uruchamiając pętlę zajętości w wątku w tle, aby obciążenie wydawało się bardziej wymagające. Jest to zła praktyka, ponieważ marnuje energię i zwiększa obciążenie termiczne urządzenia, gdy aplikacja nie używa dodatkowych zasobów. Interfejs API PerformanceHint procesora został zaprojektowany, aby rozwiązać ten problem. Informując system o rzeczywistym i docelowym czasie trwania zadania, Android może uzyskać przegląd potrzeb aplikacji w zakresie procesora i efektywnie przydzielać zasoby. Dzięki temu można osiągnąć optymalną wydajność przy efektywnym zużyciu energii.

Typy mięśni głębokich

Typy 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 obciążenia. 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 bez przejścia w stan termiczny, który nie jest zrównoważony.

Aplikacja nie powinna próbować ustawiać powinowactwa rdzeni procesora z tych powodów:

  • Najlepszy typ rdzenia dla obciążenia różni się w zależności od modelu urządzenia.
  • Zrównoważone działanie większych rdzeni różni się w zależności 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.
  • Wybór rdzenia nie może uwzględniać nowych urządzeń z dodatkowymi możliwościami w zakresie wydajności i termiki. W rezultacie urządzenia często ignorują powinowactwo procesora aplikacji.

Przykład domyślnego zachowania harmonogramu systemu Linux

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

Interfejs API PerformanceHint abstrahuje od opóźnień DVFS

ADPF abstrahuje więcej 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 API PerformanceHint wie, jak podjąć tę decyzję w Twoim imieniu.
  • Dlatego nie musisz używać powinowactwa.
  • 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, na 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 w zakresie taktowania procesora i typu 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 aplikacja używa tego interfejsu API wraz z monitorowaniem stanu termicznego, może przekazywać systemowi operacyjnemu bardziej świadome wskazówki zamiast używać pętli zajętości i innych technik kodowania, które mogą powodować ograniczanie przepustowości.

Oto jak przełożyć teorię na praktykę:

Inicjowanie PerformanceHintManager i tworzenieHintSession

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

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 premiery:

Android 11 (poziom 34 interfejsu API)

Gdy masz inne wątki , które trzeba dodać później, użyj setThreads funkcji PerformanceHintManager.Session. Jeśli na przykład utworzysz później wątek fizyki i musisz dodać go do sesji, możesz użyć tego 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 interfejsu API, musisz zniszczyć sesję i utworzyć nową za każdym razem, gdy chcesz zmienić identyfikatory wątków.

Zgłaszanie rzeczywistego czasu trwania zadania

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

Aby niezawodnie uzyskać rzeczywisty czas, 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();

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 zadania

Za każdym razem, gdy zmieni się docelowy czas trwania zadania, np. jeśli odtwarzacz wybierze inny docelowy FPS, wywołaj updateTargetWorkDurationmetodę, aby poinformować o tym system. Dzięki temu system operacyjny może dostosować zasoby do nowego celu. Nie musisz wywoływać tej funkcji w każdej klatce. Wystarczy, że wywołasz ją, gdy zmieni się docelowy czas trwania.

C++

APerformanceHint_updateTargetWorkDuration(hint_session, target_duration);

Java

hintSession.updateTargetWorkDuration(targetDuration);