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), чтобы обеспечить максимальную производительность на ватт.

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

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

Решение

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

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

  1. Создайте сеансы подсказок для ключевых потоков, которые ведут себя аналогичным образом. Например:
    • Поток рендеринга и его зависимости получают один сеанс
      1. В Cocos основной поток движка и поток рендеринга получают один сеанс.
      2. В Unity интегрируйте плагин Adaptive Performance Android Provider.
      3. В Unreal интегрируйте плагин Unreal Adaptive Performance и используйте параметры масштабируемости для поддержки нескольких уровней качества.
    • Потоки ввода-вывода получают еще один сеанс
    • Аудио-темы получают третью сессию
  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);