借助 CPU 性能提示,游戏可以影响动态 CPU 性能行为,以更好地满足自身需求。在大多数设备上,Android 会根据先前的需求为工作负载动态调整 CPU 时钟速度和核心类型。如果工作负载使用的 CPU 资源较多,则时钟速度会提高,且工作负载最终会移至更大的核心。如果工作负载使用的资源较少,Android 会调低资源分配量。借助 ADPF,应用或游戏可以发送有关其性能和截止期限的额外信号。这有助于系统更积极地提升时钟频率(提高性能),并在工作负载完成时快速降低时钟频率(节省电量)。
时钟速度
当 Android 设备动态调整其 CPU 时钟速度时,频率可能会改变代码的性能。设计可处理动态时钟速度的代码对于最大限度地提高性能、保持安全的热状态以及高效使用电量非常重要。您无法在应用代码中直接分配 CPU 频率。因此,应用尝试以较高的 CPU 时钟速度运行的一种常用方法是在后台线程中运行忙循环,使工作负载看起来需要更多资源。这是一种不良做法,因为当应用实际上未使用其他资源时,这会浪费电量并增加设备上的热负载。CPU PerformanceHint API 旨在解决此问题。通过让系统了解实际工作时长和目标工作时长,Android 将能够大致了解应用的 CPU 需求并高效地分配资源。这样一来,设备就能以高效的能耗水平实现最佳性能。
核心类型
运行游戏的 CPU 核心类型是影响性能的另一项重要因素。Android 设备通常会根据近期的工作负载行为动态更改分配给线程的 CPU 核心。在具有多个核心类型的 SoC 上,CPU 核心分配会更加复杂。在某些此类设备上,较大的核心仅供短暂使用,而不会陷入热不可持续状态。
您的游戏不应出于以下原因尝试设置 CPU 核心亲和性:
工作负载的最佳核心类型因设备型号而异。
运行较大核心的可持续性因 SoC 和每个设备型号提供的不同散热解决方案而异。
对热状态的环境影响可能使核心选择变得更加复杂。例如,天气或手机壳可能会改变设备的热状态。
核心选择无法适应具有额外的性能和散热功能的新设备。因此,设备通常会忽略游戏的处理器亲和性。
默认 Linux 调度器行为示例
图 1. 调节器可能需要大约 200 毫秒来提高或降低 CPU 频率。ADPF 与动态电压和频率调节系统 (DVFS) 协同工作,以提供最佳的每瓦性能
PerformanceHint API 不仅抽象了 DVFS 延迟时间
图 2. ADPF 知道如何代表您做出最佳决策
如果任务需要在特定 CPU 上运行,PerformanceHint API 会知道如何代表您做出该决定。
因此,您无需使用亲和性。
设备具有各种拓扑结构;电源和散热特性过于多样化,无法向应用开发者公开。
您无法对所运行的底层系统做出任何假设。
解决方案
ADPF 提供了 PerformanceHintManager 类,以便游戏可以针对 CPU 时钟速度和核心类型向 Android 发送性能提示。然后,操作系统可以根据设备的 SoC 和散热解决方案决定如何充分利用这些提示。如果您的应用将此 API 与热状态监控功能结合使用,则可以为操作系统提供更明智的提示,而无需使用忙循环和其他可能导致受限制的编码方法。
int[]tids=newint[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 时,都需要销毁会话并重新创建新会话。
clock_gettime(CLOCK_MONOTONIC,&clock);// if you prefer "C" way from <time.h>// orstd::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, ...)`autostart_time=std::chrono::high_resolution_clock::now();// do workautoend_time=std::chrono::high_resolution_clock::now();autoduration=std::chrono::duration_cast<std::chrono::nanoseconds>(end_time-start_time).count();int64_tactual_duration=static_cast<int64_t>(duration);APerformanceHint_reportActualWorkDuration(hint_session,actual_duration);
Java
longstartTime=System.nanoTime();// do worklongendTime=System.nanoTime();longduration=endTime-startTime;hintSession.reportActualWorkDuration(duration);
[null,null,["最后更新时间 (UTC):2025-07-26。"],[],[],null,["# Performance Hint API\n\n**Released**:\n\nAndroid 12 (API Level 31) - [Performance Hint API](/reference/android/os/PerformanceHintManager)\n\nAndroid 13 (API Level 33) - [Performance Hint Manager in the NDK API](/ndk/reference/group/a-performance-hint)\n\n(Preview) Android 15 (DP1) - [`reportActualWorkDuration()`](/reference/android/os/PerformanceHintManager.Session#reportActualWorkDuration(android.os.WorkDuration))\n\nWith CPU performance hints, a game can influence dynamic CPU performance\nbehavior to better match its needs. On most devices, Android dynamically adjusts\nthe CPU clock speed and core type for a workload based on the previous demands.\nIf a workload uses more CPU resources, the clock speed is increased and the\nworkload is eventually moved to a larger core. If the workload uses less\nresources, then Android lowers resource allocation. With ADPF, the application\nor game can send an additional signal about its performance and deadlines. This\nhelps the system ramp up more aggressively (improving performance) and lower the\nclocks quickly when the workload is complete (saving power usage).\n\nClock speed\n-----------\n\nWhen Android devices dynamically adjust their CPU clock speed, the frequency can\nchange the performance of your code. Designing code that addresses dynamic clock\nspeeds is important for maximizing performance, maintaining a safe thermal\nstate, and using power efficiently. You cannot directly assign CPU frequencies\nin your app code. As a result, a common way for apps to attempt to run at higher\nCPU clock speeds is to run a busy loop in a background thread so the workload\nseems more demanding. This is a bad practice as it wastes power and increases\nthe thermal load on the device when the app isn't actually using the additional\nresources. The CPU `PerformanceHint` API is designed to address this problem. By\nletting the system know the actual work duration and the target work duration,\nAndroid will be able to get an overview of the app's CPU needs and allocate\nresources efficiently. This will lead to optimum performance at efficient power\nconsumption level.\n\nCore types\n----------\n\nThe CPU core types that your game runs on are another important performance\nfactor. Android devices often change the CPU core assigned to a thread\ndynamically based on recent workload behavior. CPU core assignment is even more\ncomplex on SoCs with multiple core types. On some of these devices, the larger\ncores can only be used briefly without going into a thermally unsustainable\nstate.\n\nYour game shouldn't try to set the CPU core affinity for the following reasons:\n\n- The best core type for a workload varies by device model.\n- The sustainability of running larger cores varies by SoC and by the various thermal solutions provided by each device model.\n- The environmental impact on the thermal state can further complicate core choice. For example, the weather or a phone case can change the thermal state of a device.\n- Core selection can't accommodate new devices with additional performance and thermal capabilities. As a result, devices often ignore a game's processor affinity.\n\n### Example of default Linux scheduler behavior\n\n**Figure 1.** Governor can take \\~200ms to ramp up or down CPU frequency. ADPF works with the Dynamic Voltage and Frequency Scaling system (DVFS) to provide best performance per watt\n\n### PerformanceHint API abstracts more than DVFS latencies\n\n**Figure 2.** ADPF knows how to make the best decision on your behalf\n\n- If the tasks need to run on a specific CPU, PerformanceHint API knows how to make that decision on your behalf.\n- Therefore, you need not use affinity.\n- Devices come with various topologies; Power and thermal characteristics are too varied to be exposed to app developer.\n- You can't make any assumptions about the underlying system you're running on.\n\nSolution\n--------\n\nADPF provides the [`PerformanceHintManager`](/reference/android/os/PerformanceHintManager)\nclass so games can send performance hints to Android for CPU clock speed and\ncore type. The OS can then decide how to best use the hints based on the SoC and\nthermal solution of the device. If your app uses this API along with thermal\nstate monitoring, it can provide more informed hints to the OS instead of using\nbusy loops and other coding techniques that can cause throttling.\n\nThis is how a game uses performance hints:\n\n1. [Create hint sessions](/reference/android/os/PerformanceHintManager#createHintSession(int%5B%5D,%20long)) for key threads that behave similarly. For example:\n - Rendering thread and its dependencies get one session\n 1. In Cocos, the main engine thread and render thread gets [one\n session](https://github.com/cocos/cocos-engine/blob/v3.8.5-dianchu/native/cocos/base/threading/MessageQueue.cpp)\n 2. In Unity, integrate [Adaptive Performance Android Provider plugin](https://docs.unity3d.com/Packages/com.unity.adaptiveperformance.google.android@1.2/manual/index.html)\n 3. In Unreal, integrate Unreal Adaptive Performance plugin and use [Scalability options](https://docs.unrealengine.com/4.27/en-US/TestingAndOptimization/PerformanceAndProfiling/Scalability/ScalabilityReference/) to support multiple quality levels\n - IO threads get another session\n - Audio threads get a third session\n2. The game should do this early, at least 2ms and preferably more than 4ms before a session needs increased system resources.\n3. In each hint session, predict the duration needed for each session to run. The typical duration is equivalent to a frame interval, but the app can use a shorter interval if the workload does not vary significantly across frames.\n\nHere is how to put the theory into practice:\n\n### Initialize PerformanceHintManager and createHintSession\n\nGet the manager using system service and create a hint session for your thread\nor thread group working on the same workload. \n\n### C++\n\n int32_t tids[1];\n tids[0] = gettid();\n int64_t target_fps_nanos = getFpsNanos();\n APerformanceHintManager* hint_manager = APerformanceHint_getManager();\n APerformanceHintSession* hint_session =\n APerformanceHint_createSession(hint_manager, tids, 1, target_fps_nanos);\n\n### Java\n\n int[] tids = {\n android.os.Process.myTid()\n };\n long targetFpsNanos = getFpsNanos();\n PerformanceHintManager performanceHintManager =\n (PerformanceHintManager) this.getSystemService(Context.PERFORMANCE_HINT_SERVICE);\n PerformanceHintManager.Session hintSession =\n performanceHintManager.createHintSession(tids, targetFpsNanos);\n\n### Set threads if necessary\n\n**Released**:\n\nAndroid 11 (API Level 34)\n\nUse the [`setThreads`](/reference/android/os/PerformanceHintManager.Session#setThreads(int%5B%5D))\nfunction of the `PerformanceHintManager.Session` when you have other threads\nthat need to be added later. For example, if you create your physics thread\nlater and need to add it to the session, you can use this `setThreads` API. \n\n### C++\n\n auto tids = thread_ids.data();\n std::size_t size = thread_ids_.size();\n APerformanceHint_setThreads(hint_session, tids, size);\n\n### Java\n\n int[] tids = new int[3];\n\n // add all your thread IDs. Remember to use android.os.Process.myTid() as that\n // is the linux native thread-id.\n // Thread.currentThread().getId() will not work because it is jvm's thread-id.\n hintSession.setThreads(tids);\n\nIf you are targeting lower API Levels, you will need to destroy the session and\nre-create a new session every time you need to change the thread IDs.\n\n### Report Actual Work Duration\n\nTrack the actual duration needed to complete the work in nanoseconds and report\nit to the system upon completion of the work on every cycle. For example, if\nthis is for your rendering threads, call this on every frame.\n\nTo get the actual time reliably, use: \n\n### C++\n\n clock_gettime(CLOCK_MONOTONIC, &clock); // if you prefer \"C\" way from \u003ctime.h\u003e\n // or\n std::chrono::high_resolution_clock::now(); // if you prefer \"C++\" way from \u003cchrono\u003e\n\n### Java\n\n System.nanoTime();\n\nFor example: \n\n### C++\n\n // All timings should be from `std::chrono::steady_clock` or `clock_gettime(CLOCK_MONOTONIC, ...)`\n auto start_time = std::chrono::high_resolution_clock::now();\n\n // do work\n\n auto end_time = std::chrono::high_resolution_clock::now();\n auto duration = std::chrono::duration_cast\u003cstd::chrono::nanoseconds\u003e(end_time - start_time).count();\n int64_t actual_duration = static_cast\u003cint64_t\u003e(duration);\n\n APerformanceHint_reportActualWorkDuration(hint_session, actual_duration);\n\n### Java\n\n long startTime = System.nanoTime();\n\n // do work\n\n long endTime = System.nanoTime();\n long duration = endTime - startTime;\n\n hintSession.reportActualWorkDuration(duration);\n\n### Update Target Work Duration when necessary\n\nWhenever your target work duration changes, for example if the player chooses a\ndifferent target fps, call the [`updateTargetWorkDuration`](/reference/android/os/PerformanceHintManager.Session#updateTargetWorkDuration(long))\nmethod to let the system know so that the OS can adjust the resources according\nto the new target. You don't have to call it on every frame and only need to\ncall it when the target duration changes. \n\n### C++\n\n APerformanceHint_updateTargetWorkDuration(hint_session, target_duration);\n\n### Java\n\n hintSession.updateTargetWorkDuration(targetDuration);"]]