Performance Hint API

リリース日:

Android 12(API レベル 31)- パフォーマンス ヒント 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 コアの種類も、もう 1 つの重要なパフォーマンス要因です。Android デバイスは、一般に、スレッドに割り当てられた CPU コアを最近のワークロード動作に基づいて動的に変更します。コアの種類が複数ある SoC では、CPU コアの割り当てはさらに複雑になります。これらのデバイスの一部では、大きなコアは短時間しか使用できず、温度的に持続できない状態になることはありません。

ゲームでは、次の理由により CPU コア アフィニティを設定しないようにしてください。

  • ワークロードに最適なコアの種類は、デバイスのモデルによって異なります。
  • 大きなコアの実行の持続可能性は、SoC によって異なります。また、各デバイスのモデルに用意されている各種の温度ソリューションによっても異なります。
  • 環境が温度状態に与える影響は、コアの選択をさらに複雑にする可能性があります。たとえば、天気やスマートフォン ケースにより、デバイスの温度状態が変わることがあります。
  • コアの選択は、パフォーマンス機能や温度機能が追加された新しいデバイスには適応しない可能性があります。このため、デバイスは一般に、ゲームのプロセッサ アフィニティを無視します。

デフォルトの Linux スケジューラの動作の例

Linux スケジューラの動作
図 1. ガバナーが CPU 周波数を増減させるのに約 200 ミリ秒かかることがあります。ADPF は、Dynamic Voltage and Frequency Scaling(DVFS)システムと連携して、ワットあたりの最高のパフォーマンスを実現します。

PerformanceHint API は DVFS レイテンシ以上のものを抽象化する

ADPF は DVFS レイテンシよりも抽象化
図 2. ADPF は、ユーザーに代わって最適な判断を下す方法を認識しています。
  • タスクを特定の CPU で実行する必要がある場合、PerformanceHint API は、ユーザーに代わってその決定を行う方法を認識しています。
  • したがって、アフィニティを使用する必要はありません。
  • デバイスにはさまざまなトポロジがあります。電力と熱特性は多様すぎて、アプリ デベロッパーに公開できません。
  • 実行している基盤となるシステムについて、前提条件を設けることはできません。

解決策

ADPF の PerformanceHintManager クラスによって、ゲームは CPU クロック速度とコアの種類について Android にパフォーマンスのヒントを送信できます。OS はデバイスの SoC と温度ソリューションに基づき、そのヒントを最適に使用する方法を判断できます。アプリがこの API と温度状態のモニタリングを使用すれば、スロットリングを招く可能性があるビジーループやその他のコーディング手法を使用する代わりに、OS にさらに多くの情報を含むヒントを提供できます。

ゲームがパフォーマンスのヒントを使用する仕組みは以下のとおりです。

  1. 同じように動作する主要スレッドのヒント セッションを作成します。たとえば、次のようになります。
    • レンダリング スレッドとその依存関係が 1 つのセッションを取得する
      1. Cocos では、メイン エンジン スレッドとレンダリング スレッドが1 つのセッションを取得します。
      2. Unity で Adaptive Performance Android Provider プラグインを統合します。
      3. Unreal で、Unreal Adaptive Performance プラグインを統合し、スケーラビリティ オプションを使用して複数の品質レベルをサポートする
    • IO スレッドが別のセッションを取得する
    • オーディオ スレッドが 3 番目のセッションを取得する
  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 を変更するたびにセッションを破棄して新しいセッションを作成する必要があります。

Report Actual Work Duration(実際の作業時間を報告)

作業の完了に必要な実際の時間をナノ秒単位で追跡し、各サイクルの作業完了時にシステムに報告します。たとえば、これがレンダリング スレッド用である場合は、すべてのフレームでこれを呼び出します。

実際の時間を確実に取得するには、次のようにします。

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 メソッドを呼び出してシステムに通知し、OS が新しい目標に合わせてリソースを調整できるようにします。すべてのフレームで呼び出す必要はなく、目標期間が変更された場合にのみ呼び出す必要があります。

C++

APerformanceHint_updateTargetWorkDuration(hint_session, target_duration);

Java

hintSession.updateTargetWorkDuration(targetDuration);