已发布:
Android 12 (API Level 31) - 性能提示 API
Android 13 (API Level 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 调度器行为示例

PerformanceHint API 抽象了 DVFS 延迟之外的更多内容

- 如果任务需要在特定 CPU 上运行,PerformanceHint API 知道如何代您做出该决策。
- 因此,您无需使用亲和性。
- 设备具有各种拓扑;电源和散热特性过于多样化,不适合向应用开发者公开。
- 您无法对所运行的底层系统做出任何假设。
解决方案
ADPF 提供了 PerformanceHintManager
类,以便游戏可以向 Android 发送 CPU 时钟速度和核心类型的性能提示。然后,操作系统可以根据设备的 SoC 和散热解决方案决定如何最好地使用这些提示。如果您的应用将此 API 与热状态监控结合使用,它可以向操作系统提供更明智的提示,而不是使用忙循环和其他可能导致节流的代码技术。
以下是游戏如何使用性能提示
- 为行为相似的关键线程创建提示会话。例如
- 渲染线程及其依赖项获得一个会话
- 在 Cocos 中,主引擎线程和渲染线程获得一个会话
- 在 Unity 中,集成 Adaptive Performance Android Provider 插件
- 在 Unreal 中,集成 Unreal Adaptive Performance 插件并使用可伸缩性选项来支持多个质量级别
- IO 线程获得另一个会话
- 音频线程获得第三个会话
- 渲染线程及其依赖项获得一个会话
- 游戏应尽早执行此操作,至少在会话需要更多系统资源之前 2 毫秒,最好是 4 毫秒以上。
- 在每个提示会话中,预测每个会话运行所需的持续时间。典型持续时间等同于帧间隔,但如果工作负载在帧之间没有显著变化,则应用可以使用较短的间隔。
以下是如何将理论付诸实践
初始化 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 Level 34)
当您有其他线程需要在以后添加时,使用 PerformanceHintManager.Session
的 setThreads
函数。例如,如果您稍后创建物理线程并需要将其添加到会话中,则可以使用此 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);
必要时更新目标工作持续时间
每当您的目标工作持续时间发生变化时(例如,如果玩家选择不同的目标帧率),请调用 updateTargetWorkDuration
方法,让系统知道,以便操作系统可以根据新目标调整资源。您无需在每帧上调用它,只需在目标持续时间变化时调用它。
C++
APerformanceHint_updateTargetWorkDuration(hint_session, target_duration);
Java
hintSession.updateTargetWorkDuration(targetDuration);