Performance Hint API

发布日期

Android 12(API 级别 31)- Performance Hint API

Android 13(API 级别 33)- NDK API 中的 Performance Hint Manager

(预览版)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 调度程序行为
图 1. 调节器可能需要大约 200 毫秒的时间来增加或降低 CPU 频率。ADPF 可与动态电压和频率调节系统 (DVFS) 搭配使用,以实现最佳的单位功率性能

PerformanceHint API 对 DVFS 延迟时间的抽象化处理

ADPF 抽象多于 DVFS 延迟时间
图 2. ADPF 知道如何代表您做出最明智的决定
  • 如果任务需要在特定 CPU 上运行,PerformanceHint API 知道如何代表您做出决定。
  • 因此,您无需使用亲和性。
  • 设备具有各种拓扑;电源和热特性变化多样,无法向应用开发者公开。
  • 您不能对正在运行的底层系统做任何假设。

解决方案

ADPF 提供了 PerformanceHintManager 类,以便游戏可以向 Android 发送有关 CPU 时钟速度和核心类型的性能提示。然后,操作系统可以根据设备的 SoC 和散热解决方案决定如何充分利用这些提示。如果您的应用将此 API 与热状态监控功能结合使用,则可以为操作系统提供更明智的提示,而无需使用忙循环和其他可能导致受限制的编码方法。

游戏使用性能提示的方式如下:

  1. 为行为相似的键线程创建提示会话。例如:
  2. 游戏应尽早执行此操作,至少要比会话需要更多系统资源的时间提前 2 毫秒(最好超过 4 毫秒)。
  3. 在每个提示会话中,预测每个会话运行所需的时长。典型时长相当于帧间隔,但如果各帧的工作负载没有显著变化,则应用可以使用较短的间隔。

下面介绍如何将该理论付诸实践:

初始化 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)

如果您有稍后需要添加的其他线程,请使用 PerformanceHintManager.SessionsetThreads 函数。例如,如果您稍后创建物理线程,并且需要将其添加到会话中,可以使用此 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);