Performance Hint API

Дата выхода:

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

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

Android 15 (DP1) – reportActualWorkDuration()

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

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

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

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

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

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

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

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

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

PerformanceHint API абстрагирует больше, чем задержки DVFS

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

Решение

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

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

Инициализация PerformanceHintManager и создание сеанса подсказки

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

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

Если вы используете более низкие уровни 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);