プロファイルに基づく最適化(PGO)の仕組み

プロファイルに基づく最適化(PGO または「pogo」)は、ゲームを実際にプレイしたときの動作に関する情報を使用して、最適化済みのビルドをさらに最適化する手段です。これにより、エラーやエッジケースなどの実行頻度の低いコードがクリティカル実行パスから外され、高速化されます。

PGO の仕組みの概要を示す図

図 1. PGO の仕組みの概要

PGO を使用するには、まず、コンパイラが処理できるプロファイル データを生成するために、ビルドを計測可能にします。次に、そのビルドを実行して 1 つ以上のプロファイル データ ファイルを生成することで、コードの計測実行を行います。最後に、これらのファイルをデバイスからコピーし、コンパイラで使用して、取得したプロファイル情報を利用して実行ファイルを最適化します。

PGO を使用せずにビルドを最適化する方法

プロファイル データを使用せずにビルドを最適化する場合、最適化されたコードの生成方法を決定する際に、多くのヒューリスティクスが使用されます。

一部はデベロッパーが明示的に示します。たとえば、C++ 20 以降では、[[likely]][[unlikely]] などの分岐方向のヒントを使用して行います。別の例として、inline キーワードを使用する場合や __forceinline を使用する場合さえあります(通常は前者にとどめるが賢明であり、柔軟性もあります)。一部のコンパイラは、デフォルトで分岐の 1 つ目の枝(else 部ではなく if ステートメント)を最も可能性が高いと想定します。また、オプティマイザーは、コードの静的解析からコードの実行について推測することもできますが、通常そのスコープは限定されます。

こういったヒューリスティクスの問題は、すべてに手動のマークアップを行っても、あらゆる状況でコンパイラを正しくサポートできるわけではないことです。そのため、生成されるコードはある程度最適化されますが、コンパイラが実行時の動作に関する詳しい情報を持っている場合ほどではありません。

プロファイルの生成

計測モードで PGO を有効にして実行ファイルをビルドすると、各コードブロックの先頭(関数の先頭や分岐の各枝の先頭など)にコードが追加されます。このコードは、コードの実行によるブロックへの進入をすべてカウントし、それをコンパイラが後で最適化されたコードを生成する際に使用します。

その他のトラッキングも行われます。たとえば、ブロック内の典型的なコピー操作のサイズを把握し、後でその操作の高速なインライン版を生成できるようにするなどです。

ゲームでなんらかの代表的な処理が行われた後で、実行ファイルが関数(__llvm_profile_write_file())を呼び出して、デバイス上のカスタマイズ可能な場所にプロファイル データを書き出す必要があります。この関数は、ビルド構成で PGO 計測が有効になっていると、ゲームに自動的にリンクされます。

書き込まれたプロファイル データ ファイルは、ホスト コンピュータにコピーし、一緒に使用できるように同じビルドの他のプロファイルとともに保存することをおすすめします。

たとえば、現在のゲームシーンが終了したときに __llvm_profile_write_file() を呼び出すようにゲームコードを変更します。次に、プロファイルを作成するために、計測をオンにしてゲームをビルドし、Android デバイスにデプロイします。実行中はプロファイル データが自動的にキャプチャされます。QA エンジニアは、ゲームのランスルーを行い、さまざまなシナリオを実行します(または通常のテストパスを進みます)。

ゲーム各部の実行が終わったら、メインメニューに戻ります。それにより現在のシーンが終了し、プロファイル データが書き出されます。

その後、スクリプトを使用してプロファイル データをテストデバイスの外にコピーし、後で使用するために取得できる中央リポジトリにアップロードします。

プロファイル データのマージ

デバイスからプロファイルを取得したら、計測ビルドによって生成されたプロファイル データ ファイルから、コンパイラが使用できる形式に変換する必要があります。これは、プロジェクトに追加するすべてのプロファイル データ ファイルに対して AGDE が自動的に行います。

PGO は、複数の計測プロファイル実行の結果を組み合わせるように設計されています。1 つのプロジェクトに複数のファイルがある場合、AGDE はこれを自動的に行います。

プロファイル データ セットのマージが有用な例として、ラボで多数の QA エンジニアがゲームの別々のレベルをプレイしている場合があります。各プレイは記録され、ゲームの PGO 計測ビルドからプロファイル データを生成するために使用されます。プロファイルをマージすると、このような異なるテスト実行のすべての結果を組み合わせることができ、コードのさまざまな部分を幅広く実行し、良い結果を得ることができます。

さらに、内部リリース間でプロファイル データを保持して縦断テストを実行する場合、ビルドし直しても古いプロファイル データは必ずしも無効にはなりません。ほとんどの場合、リリース間では比較的コードが安定しているため、古いビルドのプロファイル データも引き続き使用可能であり、すぐには陳腐化しません。

プロファイルに基づいて最適化されたビルドの生成

プロファイル データをプロジェクトに追加したら、ビルド構成の最適化モードで PGO を有効にして実行ファイルをビルドすることができます。

これにより、コンパイラのオプティマイザーは、最適化に関する判断を行う際、以前に取得したプロファイル データを使用するようになります。

プロファイルに基づく最適化を使用すべきタイミング

PGO は、開発の開始時や日々のコード改良中に有効にすべきものではありません。開発中は、大きなメリットが得られるアルゴリズムとデータ レイアウトに基づいた最適化に重点を置くべきです。

PGO は、リリースに向けた改良を行う、開発プロセスの後半で行います。プロファイルに基づく最適化は、ある程度の時間をかけてコードを最適化した後で、コードからパフォーマンスの最後の一滴を絞り出すための仕上げなのです。

PGO で想定されるパフォーマンスの向上

これは、プロファイルがどの程度包括的でどの程度陳腐化しているか、従来の最適化済みビルドでどの程度コードが最適に近いかなど、さまざまな要因に左右されます。

一般的に、かなり控えめな見積もりをしても、主要なスレッドでの CPU コストが 5% ほど削減されます。結果は異なる場合があります。

計測のオーバーヘッド

PGO の計測は包括的であり、自動的に生成されますが、無コストではありません。PGO 計測のオーバーヘッドは、コードベースによって大きく異なります。

プロファイルに基づく計測のパフォーマンス コスト

計測ビルドを使用すると、フレームレートが低下することがあります。通常の動作中の CPU 使用率がどの程度 100% に近いかにもよりますが、通常のゲームプレイが難しくなる場合があります。

ほとんどのデベロッパーには、自身のゲームに半決定論的リプレイモードを設けることをおすすめします。この種の機能が備わっていると、QA チームがゲーム内の反復可能な既知の開始場所(保存済みゲームや特定のテストレベルなど)からゲームを開始し、入力を記録できます。テストビルドで記録されたこの入力は、PGO 測定ビルドに供給されてリプレイされ、個々のフレームの処理にかかる時間に関係なく(ゲームの動作が遅すぎてプレイできない場合でも)実際のプロファイル データを生成できます。

この種の機能には他にも、複数テスターの協力など、大きな利点があります(1 人のテスターがデバイスで入力を記録して、それを各種のデバイスでリプレイしてスモークテストを行う)。

このようなリプレイ システムは、エコシステムに多種のデバイスがある Android には非常に大きなメリットがあります。さらには、継続的インテグレーションのビルドシステムで中核をなす要素となり、夜間の定期的なパフォーマンス回帰テストやスモークテストも実施できるようになります。

この記録では、ゲームの入力メカニズム内の最適な場所でユーザー入力を記録する必要があります(通常は、タッチスクリーン イベントを直接記録するのではなく、その結果のコマンドを記録します)。また、これらの入力では、ゲームプレイ中に単調増加するフレーム カウントも記録し、再生中にリプレイ メカニズムがイベントをトリガーすべきフレームを待機できるようにする必要があります。

再生モードでは、オンラインでのログインを回避し、広告を表示せず、固定の時間ステップで(ターゲット フレームレートで)動作させる必要があります。vsync の無効化を検討してください。

ゲームのすべて(パーティクル システムなど)が完全に決定論的な反復可能性を備えている必要はありませんが、同じアクションであれば、ゲームでの結果は同じになる、つまりゲームプレイが同じになる必要があります。

プロファイルに基づく測定のメモリコスト

PGO 計測のメモリ オーバーヘッドは、組み込まれるライブラリによって大きく異なります。Google のテストでは、テスト実行ファイルのサイズが全体で約 2.2 倍に増加しました。このサイズ増加には、コードブロックの測定に必要な追加のコードと、カウンタの保存に必要なスペースの両方が含まれています。これらのテストはすべてを網羅したものではないため、実際のコストは異なる場合があります。

プロファイル データを更新または破棄するタイミング

コード(またはゲーム コンテンツ)に大きな変更を加えたときには、プロファイルを更新する必要があります。

厳密には、ビルド環境と、開発工程のどの段階にあるかによって異なります。

前述のように、ビルド環境に大きな変更があった場合にはプロファイル データを再利用しないでください。ビルドができなくなったり、ビルドに不具合が発生することはありませんが、新しいビルド環境に適用できるプロファイル データはわずかであるため、PGO を使用したことによるパフォーマンス上のメリットが損なわれます。なお、これ以外にもプロファイル データが陳腐化するケースはあります。

まず、リリース前の開発終了間際まで PGO を使用せず、パフォーマンス担当のエンジニアがリリース近くになって予期せぬ問題が発生していないかを確認するために毎週キャプチャを収集するだけだとしましょう。

そして、リリース ウィンドウに近づくと、QA チームが毎日テストを行い、ゲームの網羅的なランスルーを行います。このフェーズでは、そのデータから毎日プロファイルを生成し、その情報をその後のビルドのパフォーマンス テストやパフォーマンス バジェットの調整に役立てます。

リリース準備の際には、リリース予定のビルド バージョンをロックし、QA がランスルーを行って、新しいプロファイル データを生成する必要があります。このデータを使用してビルドし、最終版の実行ファイルを生成します。

そして、QA がこの最適化済みのリリース向けビルドの最終的なランスルーを行い、リリースしても問題ないことを確認します。