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

Выпущенный :

Android 12 (уровень API 31) - PerformanceHintManager

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 вместе с мониторингом состояния температуры, оно может предоставлять ОС более информативные подсказки, вместо использования циклов ожидания и других методов кодирования, которые могут вызывать снижение производительности.

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

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

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

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

При необходимости настройте потоки.

Выпущенный :

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

Используйте функцию setThreads объекта PerformanceHintManager.Session , если вам нужно добавить другие потоки позже. Например, если вы создадите поток физики позже и вам потребуется добавить его в сессию, вы можете использовать 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);

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

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

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

Для получения достоверного времени используйте:

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

Например:

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

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

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

C++

APerformanceHint_updateTargetWorkDuration(hint_session, target_duration);

Java

hintSession.updateTargetWorkDuration(targetDuration);