发布日期:
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 调度程序行为](https://developer.android.google.cn/static/games/optimize/adpf/images/adpf_linux_sched_behavior.png?authuser=6&hl=da)
PerformanceHint API 对 DVFS 延迟时间的抽象化处理
![ADPF 抽象多于 DVFS 延迟时间](https://developer.android.google.cn/static/games/optimize/adpf/images/adpf_dvfs_latency.png?authuser=6&hl=da)
- 如果任务需要在特定 CPU 上运行,PerformanceHint API 知道如何代表您做出决定。
- 因此,您无需使用亲和性。
- 设备具有各种拓扑;电源和热特性变化多样,无法向应用开发者公开。
- 您不能对正在运行的底层系统做任何假设。
解决方案
ADPF 提供了 PerformanceHintManager
类,以便游戏可以向 Android 发送有关 CPU 时钟速度和核心类型的性能提示。然后,操作系统可以根据设备的 SoC 和散热解决方案决定如何充分利用这些提示。如果您的应用将此 API 与热状态监控功能结合使用,则可以为操作系统提供更明智的提示,而无需使用忙循环和其他可能导致受限制的编码方法。
游戏使用性能提示的方式如下:
- 为行为相似的键线程创建提示会话。例如:
- 渲染线程及其依赖项会获得一个会话
- 在 Cocos 中,主线程和渲染线程获取一个会话
- 在 Unity 中,集成 Adaptive Performance Android Provider 插件
- 在 Unreal 中,集成 Unreal 自适应性能插件,并使用可伸缩性选项来支持多个质量级别
- 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 级别 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);
根据需要更新目标工作时长
每当目标工作时长发生变化时(例如,如果玩家选择了不同的目标 fps),请调用 updateTargetWorkDuration
方法告知系统,以便操作系统可以根据新的目标调整资源。您不必在每一帧上都调用它,只需在目标时长发生变化时调用它即可。
C++
APerformanceHint_updateTargetWorkDuration(hint_session, target_duration);
Java
hintSession.updateTargetWorkDuration(targetDuration);