發布日期:
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 核心。CPU 核心指派作業在含有多種核心類型的 SoC 上更為複雜。在部分這類裝置上,系統可能只會短暫使用大型核心,以避免進入因過熱而無法維持永續等級的狀態。
您不應該嘗試對遊戲設定 CPU 核心相依性,原因如下:
- 特定工作負載的最佳核心類型會因裝置型號而異。
- 執行大型核心的永續做法會因 SoC 和每個裝置型號提供的各種熱能解決方案而異。
- 環境對熱力狀態的影響可能會使核心選擇更加複雜。舉例來說,天氣或手機保護殼可能會改變裝置的熱力狀態。
- 核心選擇無法因應具備額外效能和溫度調整功能的新裝置。因此,裝置通常會忽略遊戲的處理器相依性。
預設 Linux 排程器行為範例
PerformanceHint API 簡化了超過 DVFS 的延遲時間
- 如果工作必須在特定的 CPU 上執行,PerformanceHint API 知道如何代表您做出決定。
- 因此,您不需要使用相依性。
- 裝置具有各種拓撲;無法讓應用程式開發人員瞭解其電源和熱能特性。
- 您無法對正在執行的基礎系統有任何假設。
解決方案
ADPF 提供 PerformanceHintManager
類別,可讓遊戲向 Android 傳送 CPU 時脈速度和核心類型的效能提示。這樣一來,OS 就可以根據裝置的 SoC 和熱能解決方案,決定如何以最佳方式因應這項提示。如果應用程式搭配熱力狀態監控使用這個 API,就能為 OS 提供更多資訊提示,而不必使用可能引發過熱保護的忙碌迴圈和其他程式設計技巧。
以下說明遊戲使用效能提示的方式:
- 為行為相似的主要執行緒建立提示工作階段。例如:
- 轉譯執行緒及其依附元件會取得一個工作階段
- 在 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 級別 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
方法,讓系統通知系統,讓 OS 根據新目標調整資源。您不需要在每個影格上呼叫此函式,只需在目標時間長度變更時呼叫此函式即可。
C++
APerformanceHint_updateTargetWorkDuration(hint_session, target_duration);
Java
hintSession.updateTargetWorkDuration(targetDuration);