API подсказок по производительности

Выпущенный :

Android 12 (API уровня 31) — API подсказок по производительности

Android 13 (API уровня 33) — Менеджер подсказок по производительности в API NDK

(Предварительный просмотр) Android 15 (DP1) — reportActualWorkDuration()

С помощью подсказок по производительности ЦП игра может влиять на динамическое поведение производительности ЦП, чтобы лучше соответствовать своим потребностям. На большинстве устройств Android динамически подстраивает тактовую частоту ЦП и тип ядра под рабочую нагрузку в зависимости от предыдущих потребностей. Если рабочая нагрузка потребляет больше ресурсов ЦП, тактовая частота увеличивается, и нагрузка в конечном итоге перемещается на более мощное ядро. Если рабочая нагрузка потребляет меньше ресурсов, Android уменьшает распределение ресурсов. С помощью ADPF приложение или игра могут отправлять дополнительные сигналы о своей производительности и сроках выполнения. Это помогает системе более агрессивно наращивать производительность (повышая производительность) и быстро снижать тактовую частоту после завершения рабочей нагрузки (экономя энергопотребление).

Тактовая частота

Когда устройства Android динамически изменяют тактовую частоту своего процессора, эта частота может влиять на производительность вашего кода. Разработка кода, учитывающего динамическую тактовую частоту, важна для максимизации производительности, поддержания безопасного теплового состояния и эффективного использования энергии. Вы не можете напрямую назначать частоту процессора в коде приложения. В результате приложения часто пытаются работать на более высоких тактовых частотах процессора, запуская загруженный цикл в фоновом потоке, чтобы рабочая нагрузка казалась более требовательной. Это плохая практика, поскольку она приводит к напрасному расходу энергии и увеличению тепловой нагрузки на устройство, когда приложение фактически не использует дополнительные ресурсы. API CPU PerformanceHint предназначен для решения этой проблемы. Сообщая системе фактическую и целевую длительность работы, Android сможет получить представление о потребностях приложения в процессоре и эффективно распределять ресурсы. Это приведет к оптимальной производительности при эффективном уровне энергопотребления.

Основные типы

Типы ядер процессора, на которых работает ваша игра, — ещё один важный фактор производительности. Устройства на Android часто динамически меняют ядро процессора, назначенное потоку, в зависимости от текущей нагрузки. Назначение ядер процессора ещё сложнее в системах на кристалле (SoC) с несколькими типами ядер. На некоторых устройствах более крупные ядра могут использоваться лишь кратковременно, не переходя в состояние тепловой нестабильности.

Ваша игра не должна пытаться установить привязку к ядрам ЦП по следующим причинам:

  • Оптимальный тип ядра для рабочей нагрузки зависит от модели устройства.
  • Устойчивость работы более крупных ядер зависит от SoC и различных тепловых решений, предоставляемых каждой моделью устройства.
  • Влияние окружающей среды на тепловое состояние может ещё больше усложнить выбор ядра. Например, погода или чехол для телефона могут изменить тепловое состояние устройства.
  • Выбор ядра не подходит для новых устройств с повышенной производительностью и тепловыми характеристиками. В результате устройства часто игнорируют совместимость процессора с игрой.

Пример поведения планировщика Linux по умолчанию

Поведение планировщика Linux
Рисунок 1. Регулятору частоты требуется около 200 мс для повышения или понижения частоты процессора. ADPF работает с системой динамического масштабирования напряжения и частоты (DVFS), обеспечивая максимальную производительность на ватт.

API PerformanceHint абстрагируется не только от задержек DVFS

ADPF обрабатывает больше задержек, чем DVFS
Рисунок 2. ADPF знает, как принять лучшее решение от вашего имени
  • Если задачи должны выполняться на определенном ЦП, API PerformanceHint знает, как принять это решение от вашего имени.
  • Поэтому вам не нужно использовать сродство.
  • Устройства поставляются с различными топологиями; характеристики мощности и тепловые характеристики слишком разнообразны, чтобы предоставлять их разработчику приложения.
  • Вы не можете делать никаких предположений относительно базовой системы, на которой вы работаете.

Решение

ADPF предоставляет класс PerformanceHintManager , позволяющий играм отправлять Android подсказки о производительности, включая тактовую частоту процессора и тип ядра. Операционная система может решить, как лучше всего использовать эти подсказки, исходя из особенностей SoC и системы охлаждения устройства. Если ваше приложение использует этот API вместе с мониторингом теплового состояния, оно может предоставлять ОС более обоснованные подсказки, избегая использования циклов и других методов кодирования, которые могут привести к троттлингу.

Вот как игра использует подсказки по производительности:

  1. Создавайте сеансы подсказок для ключевых потоков, которые ведут себя схожим образом. Например:
    • Поток рендеринга и его зависимости получают один сеанс
      1. В Cocos основной поток движка и поток рендеринга получают один сеанс
      2. Интегрируйте плагин Adaptive Performance Android Provider в Unity.
      3. Интегрируйте плагин Unreal Adaptive Performance в Unreal и используйте параметры масштабируемости для поддержки нескольких уровней качества.
    • Потоки ввода-вывода получают еще один сеанс
    • Аудиопотоки получают третий сеанс
  2. Игра должна делать это заранее, по крайней мере за 2 мс, а лучше за 4 мс, прежде чем сеансу потребуются увеличенные системные ресурсы.
  3. В каждом сеансе подсказок определите продолжительность, необходимую для выполнения каждого сеанса. Обычно она эквивалентна интервалу между кадрами, но приложение может использовать более короткий интервал, если нагрузка не сильно меняется между кадрами.

Вот как применить теорию на практике:

Инициализируйте PerformanceHintManager и создайтеHintSession

Получите менеджера, использующего системную службу, и создайте сеанс подсказок для вашего потока или группы потоков, работающих над той же рабочей нагрузкой.

С++

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

Ява

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

При необходимости установите темы

Выпущенный :

Android 11 (уровень API 34)

Используйте функцию setThreads класса PerformanceHintManager.Session , если у вас есть другие потоки, которые нужно добавить позже. Например, если вы создадите физический поток позже и вам нужно добавить его в сеанс, вы можете использовать API setThreads .

С++

auto tids = thread_ids.data();
std::size_t size = thread_ids_.size();
APerformanceHint_setThreads(hint_session, tids, size);

Ява

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

Если вы ориентируетесь на более низкие уровни API, вам придется уничтожать сеанс и заново создавать новый сеанс каждый раз, когда вам потребуется изменить идентификаторы потоков.

Отчет о фактической продолжительности работы

Отслеживайте фактическую длительность выполнения работы в наносекундах и сообщайте её системе по завершении работы в каждом цикле. Например, если это относится к потокам рендеринга, вызывайте это в каждом кадре.

Чтобы точно узнать фактическое время, используйте:

С++

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>

Ява

System.nanoTime();

Например:

С++

// 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);

Ява

long startTime = System.nanoTime();

// do work

long endTime = System.nanoTime();
long duration = endTime - startTime;

hintSession.reportActualWorkDuration(duration);

При необходимости обновите целевую продолжительность работы.

При изменении целевой длительности работы, например, если игрок выбирает другой целевой FPS, вызовите метод updateTargetWorkDuration , чтобы сообщить об этом системе и скорректировать ресурсы в соответствии с новой целевой частотой. Вызывать его не нужно в каждом кадре, достаточно только при изменении целевой длительности.

С++

APerformanceHint_updateTargetWorkDuration(hint_session, target_duration);

Ява

hintSession.updateTargetWorkDuration(targetDuration);