效能提示 API

發布日期

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 排程器行為範例

Linux 排程器行為
圖 1 管理人員可能需要大約 200 毫秒才能增加或降低 CPU 頻率。ADPF 可與動態電壓和頻率縮放系統 (DVFS) 搭配使用,以提供每瓦特最佳效能

PerformanceHint API 簡化了超過 DVFS 的延遲時間

ADPF 摘要比 DVFS 延遲時間多
圖 2. ADPF 知道如何代您做出最佳決定
  • 如果工作必須在特定的 CPU 上執行,PerformanceHint API 知道如何代表您做出決定。
  • 因此,您不需要使用相依性。
  • 裝置具有各種拓撲;無法讓應用程式開發人員瞭解其電源和熱能特性。
  • 您無法對正在執行的基礎系統有任何假設。

解決方案

ADPF 提供 PerformanceHintManager 類別,可讓遊戲向 Android 傳送 CPU 時脈速度和核心類型的效能提示。這樣一來,OS 就可以根據裝置的 SoC 和熱能解決方案,決定如何以最佳方式因應這項提示。如果應用程式搭配熱力狀態監控使用這個 API,就能為 OS 提供更多資訊提示,而不必使用可能引發過熱保護的忙碌迴圈和其他程式設計技巧。

以下說明遊戲使用效能提示的方式:

  1. 為行為相似的主要執行緒建立提示工作階段。例如:
  2. 遊戲應在工作階段需要增加系統資源至少 2 毫秒,且最好超過 4 毫秒之前,及早執行這項作業。
  3. 在每個提示工作階段中,預測每個工作階段執行所需的時間。這個時間長度一般相當於影格間隔,但如果工作負載沒有明顯的跨影格差異,應用程式可使用較短的間隔。

以下說明如何將理論付諸實行:

初始化 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.SessionsetThreads 函式。舉例來說,如果您之後建立物理執行緒,並需要將其新增至工作階段,就可以使用這個 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);