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](https://developer.android.com/static/games/optimize/adpf/images/adpf_linux_sched_behavior.png?authuser=7&hl=pl)
Interfejs PerformanceHint API pobiera więcej danych niż czasy oczekiwania w DVFS
![ADPF zajmuje więcej czasu niż opóźnienia DVFS](https://developer.android.com/static/games/optimize/adpf/images/adpf_dvfs_latency.png?authuser=7&hl=pl)
- 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:
- 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ę.
- W systemie Cocos główny wątek wyszukiwarki i wątek renderowania uzyskuje jedną sesję
- Zintegruj w Unity wtyczkę Adaptive Performance Android Provider
- 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ę
- Wątek renderowania i jego zależności uzyskują 1 sesję.
- 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.
- 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);