Interfejs Performance Hint API

Data premiery:

Android 12 (poziom 31 interfejsu API) – PerformanceHintManager

Android 13 (poziom 33 interfejsu API) – Performance Hint Manager w interfejsie 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 zadanie wykorzystuje więcej zasobów procesora, taktowanie procesora jest zwiększane, a zadanie jest ostatecznie przenoszone do większego rdzenia. Jeśli zadanie 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 zadania (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. W rezultacie powszechnym sposobem na to, aby aplikacje próbowały działać z wyższym taktowaniem procesora, jest uruchamianie pętli zajętości w wątku w tle, aby zadanie wydawało się bardziej wymagające. Jest to zła praktyka, ponieważ marnuje energię i zwiększa obciążenie termiczne urządzenia, gdy aplikacja w rzeczywistości nie używa dodatkowych zasobów. Interfejs CPU PerformanceHint API został zaprojektowany, aby rozwiązać ten problem. Dzięki informowaniu systemu o rzeczywistym i docelowym czasie trwania zadania Android będzie mógł uzyskać przegląd potrzeb aplikacji w zakresie procesora i efektywnie przydzielać zasoby. Doprowadzi to do optymalnej wydajności 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. Przypisanie rdzenia 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, aby nie doprowadzić do stanu termicznego, który nie jest zrównoważony.

Aplikacja nie powinna próbować ustawiać powinowactwa rdzenia 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 PerformanceHint API 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 PerformanceHint API 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 tworzenie sesji wskazówek

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

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 setThreads 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 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. gdy odtwarzacz wybierze inną docelową liczbę klatek na sekundę , wywołaj metodę updateTargetWorkDuration , aby poinformować o tym system. Dzięki temu system operacyjny będzie mógł dostosować zasoby do nowego celu. Nie musisz wywoływać tej metody 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);