Data premiery:
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 gra 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 ostatecznie zadanie jest przenoszone na większy rdzeń. Jeśli zadanie wykorzystuje mniej zasobów, Android zmniejsza ich przydział. Dzięki ADPF aplikacja lub gra może wysyłać dodatkowy sygnał dotyczący jej wydajności i terminów. Dzięki temu system może szybciej zwiększać wydajność i szybciej obniżać taktowanie po zakończeniu pracy (co pozwala oszczędzać energię).
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 nie korzysta z dodatkowych zasobów. Interfejs API PerformanceHint
CPU został zaprojektowany, aby rozwiązać ten problem. Gdy poinformujesz system o rzeczywistym i docelowym czasie trwania zadania, Android będzie mógł uzyskać przegląd potrzeb aplikacji w zakresie procesora i efektywnie przydzielać zasoby. Zapewni to optymalną wydajność przy niskim zużyciu energii.
Typy rdzeni
Rodzaje rdzeni procesora, na których działa gra, 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.
Gra nie powinna próbować ustawiać powiązania rdzeni procesora z tych powodów:
- Najlepszy typ rdzenia dla danego zadania zależy 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 procesora z grą.
Przykład domyślnego działania harmonogramu systemu Linux

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

- 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 gry mogą wysyłać do Androida wskazówki dotyczące wydajności w zakresie szybkości zegara 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 Twoja aplikacja korzysta z tego interfejsu API wraz z monitorowaniem stanu termicznego, może przekazywać do systemu operacyjnego bardziej precyzyjne wskazówki zamiast używać pętli zajętości i innych technik kodowania, które mogą powodować ograniczanie przepustowości.
Oto jak gra korzysta ze wskazówek dotyczących wydajności:
- Twórz sesje podpowiedzi dla kluczowych wątków, które zachowują się podobnie. Na przykład:
- Wątek renderowania i jego zależności otrzymują 1 sesję.
- W Cocos główny wątek silnika i wątek renderowania otrzymują jedną sesję.
- W Unity zintegruj wtyczkę Adaptive Performance Android Provider.
- W Unreal zintegruj wtyczkę Unreal Adaptive Performance i użyj opcji skalowalności, aby obsługiwać wiele poziomów jakości.
- Wątki wejścia/wyjścia otrzymują kolejną sesję
- Wątki audio otrzymują trzecią sesję
- Wątek renderowania i jego zależności otrzymują 1 sesję.
- Gra powinna to zrobić odpowiednio wcześnie, co najmniej 2 ms, a najlepiej ponad 4 ms przed tym, jak sesja będzie wymagać większych zasobów systemowych.
- W każdej sesji podpowiedzi przewiduj czas trwania każdej sesji. Typowy czas trwania jest równy interwałowi klatek, ale aplikacja może używać krótszego interwału, jeśli obciążenie nie różni się znacząco w przypadku poszczególnych klatek.
Aby przełożyć teorię na praktykę:
Inicjowanie interfejsu PerformanceHintManager i tworzenie sesji podpowiedzi
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.
Data premiery:
Android 11 (poziom 34 interfejsu API)
Użyj funkcji setThreads
PerformanceHintManager.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 setThreads
interfejsu 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ć o tym system. Dzięki temu system operacyjny będzie mógł 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);