透過 CPU 效能提示,遊戲可以影響動態 CPU 效能行為,更符合自身需求。在大多數裝置上,Android 會依據先前的需求,以動態方式調整工作負載的 CPU 時脈速度和核心類型。如果工作負載使用更多 CPU 資源,時脈速度就會提高,且工作負載最終會移至大型核心。如果工作負載使用的資源較少,則 Android 會減少資源分配。應用程式或遊戲可透過 ADPF 傳送額外信號,說明效能和期限。這有助於系統更積極地提高時脈 (提升效能),並在工作負載完成時快速降低時脈 (節省電力)。
時脈速度
當 Android 裝置動態調整 CPU 時脈速度時,頻率可能會變更程式碼的效能。設計可處理動態時脈速度的程式碼,對於盡可能提高效能、維護安全的熱力狀態以及有效使用電力至關重要。您無法在應用程式程式碼中直接指定 CPU 頻率。因此,如果應用程式要嘗試以較高的 CPU 時脈速度執行,常見的方法就是在背景執行緒中執行忙碌迴圈,讓工作負載看似需要更多的資源。這項做法並不理想,因為應用程式未實際使用額外資源時,會浪費電力並增加裝置的熱能負載。CPU PerformanceHint API 的設計宗旨就是解決這個問題。讓系統瞭解實際工作時間和目標工作時間,Android 就能掌握應用程式的 CPU 需求,並有效率地分配資源。這樣就能以有效率的耗電量達到最佳效能。
核心類型
執行遊戲的 CPU 核心類型是另一項重要效能因素。Android 裝置通常會依據近期的工作負載行為,動態變更指派給執行緒的 CPU 核心。CPU 核心指派作業在含有多種核心類型的 SoC 上更為複雜。在部分這類裝置上,系統可能只會短暫使用大型核心,以避免進入因過熱而無法維持永續等級的狀態。
您不應該嘗試對遊戲設定 CPU 核心相依性,原因如下:
特定工作負載的最佳核心類型會因裝置型號而異。
執行大型核心的永續做法會因 SoC 和每個裝置型號提供的各種熱能解決方案而異。
環境對熱力狀態的影響可能會使核心選擇更加複雜。舉例來說,天氣或手機保護殼可能會改變裝置的熱力狀態。
核心選擇無法因應具備額外效能和溫度調整功能的新裝置。因此,裝置通常會忽略遊戲的處理器相依性。
預設 Linux 排程器行為範例
圖 1. 調控器可能需要約 200 毫秒才能調高或調低 CPU 頻率。ADPF 會與動態電壓和頻率調度系統 (DVFS) 搭配運作,提供每瓦最佳效能
PerformanceHint API 抽象化的內容不只是 DVFS 延遲
圖 2. ADPF 會代表您做出最佳決策
如果工作需要在特定 CPU 上執行,PerformanceHint API 會代您做出這項決策。
因此不需要使用親和性。
裝置的拓撲結構各不相同,電力和熱能特性差異過大,無法向應用程式開發人員公開。
您無法對執行的基礎系統做出任何假設。
解決方案
ADPF 提供 PerformanceHintManager 類別,讓遊戲可以向 Android 傳送 CPU 時脈速度和核心類型的效能提示。這樣一來,OS 就可以根據裝置的 SoC 和熱能解決方案,決定如何以最佳方式因應這項提示。如果應用程式搭配熱力狀態監控使用這個 API,就能為 OS 提供更多資訊提示,而不必使用可能引發過熱保護的忙碌迴圈和其他程式設計技巧。
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);
視需要更新目標工作時間
每當目標工作時間長度變更時 (例如,如果播放器選擇不同的目標 FPS),請呼叫 updateTargetWorkDuration 方法通知系統,以便 OS 根據新目標調整資源。您不必在每個影格上呼叫此函式,只需要在目標時間長度變更時呼叫即可。
[null,null,["上次更新時間: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);"]]