Performance Hint API

已发布

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

PerformanceHint API 提取的不仅仅是 DVFS 延迟时间

ADPF 抽象比 DVFS 延迟时间更重要
图 2. ADPF 知道如何代表您做出最佳决定
<ph type="x-smartling-placeholder">
    </ph>
  • 如果任务需要在特定的 CPU 上运行,PerformanceHint API 知道如何 并代您做出决定。
  • 因此,您无需使用亲和性。
  • 设备附带各种拓扑;电源和散热特性 过于多样化,无法提供给应用开发者。
  • 您不能对运行应用的底层系统做出任何假设。

解决方案

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

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

  1. 针对行为相似的关键线程创建提示会话。例如:
    • 呈现线程及其依赖项获取一个会话 <ph type="x-smartling-placeholder">
        </ph>
      1. 在 Cocos 中,主线程线程和渲染线程分别是 会话
      2. 在 Unity 中,集成 Adaptive Performance Android Provider 插件
      3. 在 Unreal 中,集成 Unreal Adaptive Performance 插件并使用 可扩缩性选项:支持多个质量级别
    • IO 线程获得另一会话
    • 音频会话获得第三个会话
  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)

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