性能提示 API

已发布:

Android 12 (API 级别 31) - 性能提示 API

Android 13 (API 级别 33) - NDK API 中的性能提示管理器

(预览) Android 15 (DP1) - reportActualWorkDuration()

使用 CPU 性能提示,游戏可以影响动态 CPU 性能行为以更好地满足其需求。在大多数设备上,Android 会根据之前的需求动态调整 CPU 时钟速度和核心类型以适应工作负载。如果工作负载使用更多 CPU 资源,则时钟速度会提高,并且工作负载最终会转移到更大的核心。如果工作负载使用的资源较少,则 Android 会降低资源分配。使用 ADPF,应用程序或游戏可以发送有关其性能和期限的附加信号。这有助于系统更积极地提高性能(提高性能)并在工作负载完成后快速降低时钟速度(节省功耗)。

时钟速度

当 Android 设备动态调整其 CPU 时钟速度时,频率可能会改变代码的性能。设计处理动态时钟速度的代码对于最大限度地提高性能、保持安全的热状态和高效用电至关重要。您无法在应用程序代码中直接分配 CPU 频率。因此,应用程序尝试以更高的 CPU 时钟速度运行的一种常见方法是在后台线程中运行繁忙循环,以便工作负载看起来更繁重。这是一种不好的做法,因为它会在应用程序实际上未使用额外资源时浪费电力并增加设备的热负载。CPU PerformanceHint API 旨在解决此问题。通过让系统知道实际工作时长和目标工作时长,Android 将能够概述应用程序的 CPU 需求并高效地分配资源。这将导致在高效的功耗水平下获得最佳性能。

核心类型

游戏运行的 CPU 核心类型是另一个重要的性能因素。Android 设备通常会根据最近的工作负载行为动态更改分配给线程的 CPU 核心。在具有多种核心类型的 SoC 上,CPU 核心分配更加复杂。在某些这些设备上,较大的核心只能短暂使用,而不会进入热量无法承受的状态。

您的游戏不应尝试设置 CPU 核心关联性,原因如下:

  • 工作负载的最佳核心类型因设备型号而异。
  • 运行更大核心的可持续性因 SoC 和每个设备型号提供的各种热解决方案而异。
  • 对热状态的环境影响会进一步使核心选择复杂化。例如,天气或手机壳会改变设备的热状态。
  • 核心选择无法适应具有额外性能和热功能的新设备。因此,设备通常会忽略游戏的处理器关联性。

默认 Linux 调度程序行为示例

Linux Scheduler Behavior
图 1. 调节器可能需要约 200 毫秒才能提高或降低 CPU 频率。ADPF 与动态电压和频率缩放系统 (DVFS) 配合使用,以提供每瓦最佳性能

PerformanceHint API 抽象出超过 DVFS 延迟的内容

ADPF Abstracts more than DVFS Latencies
图 2. ADPF 知道如何代表您做出最佳决策
  • 如果任务需要在特定 CPU 上运行,PerformanceHint API 就会知道如何代表您做出该决策。
  • 因此,您无需使用关联性。
  • 设备具有各种拓扑结构;功耗和热特性差异太大,无法向应用程序开发者公开。
  • 您无法对正在运行的底层系统做出任何假设。

解决方案

ADPF 提供 PerformanceHintManager 类,以便游戏可以向 Android 发送 CPU 时钟速度和核心类型的性能提示。然后,操作系统可以根据设备的 SoC 和热解决方案决定如何最好地使用这些提示。如果您的应用使用此 API 以及热状态监控,它可以向操作系统提供更明智的提示,而不是使用繁忙循环和其他可能导致限制的编码技术。

以下是游戏如何使用性能提示:

  1. 创建提示会话 用于行为类似的关键线程。例如:
  2. 游戏应尽早执行此操作,至少在会话需要增加系统资源之前 2 毫秒,最好超过 4 毫秒。
  3. 在每个提示会话中,预测每个会话运行所需的持续时间。典型持续时间等效于帧间隔,但如果工作负载在帧之间变化不大,则应用程序可以使用较短的间隔。

以下是将理论付诸实践的方法:

初始化 PerformanceHintManager 并创建 hintSession

使用系统服务获取管理器,并为在同一工作负载上工作的线程或线程组创建一个提示会话。

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 级别,则每次需要更改线程 ID 时都需要销毁会话并重新创建一个新会话。

报告实际工作时长

以纳秒为单位跟踪完成工作所需的实际持续时间,并在每个周期工作完成后将其报告给系统。例如,如果这是针对您的渲染线程,则在每一帧上调用此函数。

要可靠地获取实际时间,请使用:

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

如有必要,更新目标工作时长

每当您的目标工作时长发生变化时,例如,如果玩家选择不同的目标 fps,请调用 updateTargetWorkDuration 方法让系统知道,以便操作系统可以根据新目标调整资源。您不必在每一帧上调用它,只需在目标持续时间发生变化时调用它即可。

C++

APerformanceHint_updateTargetWorkDuration(hint_session, target_duration);

Java

hintSession.updateTargetWorkDuration(targetDuration);