WWDC Quick Look 💓 By SwiftGGTeam
Profile and optimize your game's memory

Profile and optimize your game's memory

元の動画を見る

ハイライト

Apple GPU ソフトウェア チームの Jack Xu と Seth Lu が、ゲーム メモリの分析と最適化の方法を体系的に説明しました。内容は、メモリの基本概念から始まり、Instruments のメモリ解析ツール、Xcode のメモリ グラフ解析、Metal Debugger のリソース最適化に徐々に入っていきます。


主要内容

ゲームのメモリ問題は多くの場合、数字から始まります。Xcode メモリ レポートの現在のメモリ使用量が多くなり、デバイスに負担がかかり始め、最終的にはシステムによってゲームが抑制される可能性があります。通常、開発者の最初の反応は割り当て量に注目することですが、このセッションでは最初にこのエントリが修正されました。割り当ては仮想アドレス空間で発生し、システムによって実際に制限にカウントされるのはメモリ フットプリント (メモリ フットプリント) です。

この違いにより、調査の順序が変わります。ゲームは大きな仮想領域を割り当てることができますが、物理メモリに入るのはアクセスされているページだけです。最新の Apple デバイスのメモリ ページ粒度は 16 KiB です。システムは、ダーティ ページ、圧縮ページ、およびスワップされたページに対してゲームに課金します。クリーンなページはメモリ内に存在する可能性がありますが、ディスクから再ロードできるため、フットプリントには含まれません。

メタル ゲームにはさらに複雑な層があります。 Apple シリコンはユニファイド メモリを使用しており、CPU と GPU は同じ高速メモリ プールを共有するため、アクセスされたメタル バッファ、テクスチャ、パイプライン状態オブジェクトもダーティ メモリに入ります。テクスチャ、バッファ、ドローアブル、ヒープ割り当て、匿名 VM は、同じ分析プロセスで調べる必要があります。

Apple が提供するワークフローは 3 つのステップに分かれています。最初のステップは、Xcode メモリ ゲージとシステム API を使用して、フットプリントと利用可能なメモリを確認することです。 2 番目のステップでは、Instruments のゲーム メモリ テンプレートを使用してメモリの増加を記録します。 3 番目のステップは、Xcode メモリ デバッガーを使用して、特定の時点でのメモリ グラフをキャプチャすることです。footprintvmmapheapmalloc_historyleaksラージ オブジェクト、割り当てソース、および参照関係を検索します。最後に、Metal Debugger の Memory Viewer に入り、リソース サイズ、最近の使用時間、ピクセル フォーマット、ストレージ モード、およびヒープ エイリアスを監査します。


詳細

1. 割り当てだけを見るのではなく、まずフットプリントを確認してください

(06:53) Jack は、前半で割り当てと実際のメモリ使用量を明確に区別しました。割り当ては、ゲームによって要求される仮想アドレス空間の領域です。メモリ フットプリントは、システムによってゲームに実際に請求される物理メモリの量であり、メモリ制限を強制するときに一部の Apple プラットフォームで使用されるメトリクスでもあります。

ゲームは実行時に現在の環境にどれだけのメモリが残っているかをクエリできます。 iOS、iPadOS、tvOS、watchOS で利用可能os_proc_available_memory()

#import <os/proc.h>

API_UNAVAILABLE(macos) API_AVAILABLE(ios(13.0), tvos(13.0), watchos(6.0))
size_t os_proc_available_memory(void);

キーポイント:

  • #import <os/proc.h>プロセス関連のシステム インターフェイスを紹介します。 -API_UNAVAILABLE(macos)このクエリ インターフェイスが macOS では使用できないことを示します。 -API_AVAILABLE(...)利用可能なプラットフォームと最小システム バージョンを示します。 -os_proc_available_memory()現在のプロセスで使用可能なシステム メモリ サイズを返します。これは、リソースの読み込み戦略を動的に調整するために使用できます。

(07:07) 現在のフットプリントとライフサイクルのピークを確認したい場合は、次を使用できます。proc_pid_rusage()読むrusage_info_current

#if __has_include(<libproc.h>)
#include <libproc.h> // On macOS.
#else
#include <sys/resource.h> // On iOS, iPadOS and tvOS.
int proc_pid_rusage(int pid, int flavor, rusage_info_t *buffer)  
    __OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0);
#endif

rusage_info_current rusage_payload;
     
int ret = proc_pid_rusage(getpid(),
                          RUSAGE_INFO_CURRENT, // I.e., new RUSAGE_INFO_V6 this year.
                          (rusage_info_t *)&rusage_payload);
NSCAssert(ret == 0, @"Could not get rusage: %i.", errno); // Look up in `man errno`.
        
uint64_t footprint       = rusage_payload.ri_phys_footprint;
uint64_t footprint_peak  = rusage_payload.ri_lifetime_max_phys_footprint;

キーポイント:

  • __has_include(<libproc.h>)macOS が使用するプラットフォームに応じてヘッダー ファイルを選択しますlibproc.h
  • proc_pid_rusage(getpid(), RUSAGE_INFO_CURRENT, ...)現在のプロセスのリソース使用量をクエリします。 -rusage_info_current返されたデータはここに保持され、セッションはそれが現在の年に対応することを示しますRUSAGE_INFO_V6
  • ri_phys_footprint現在の物理的なフットプリントです。 -ri_lifetime_max_phys_footprintは、プロセスのライフサイクル中のピークのフットプリントです。

これら 2 つのインターフェイスは、ゲームのリソース管理層に組み込むのに適しています。たとえば、高品質のマップを入力する前に、使用可能なメモリを確認します。フットプリントがバジェットに近いことが検出された場合は、プリロード テクスチャを減らすか、キャッシュ制限を下げるか、視野内にないリソースのロードを遅らせます。

2. Game Memory テンプレートを使用して成長プロセスを記録する

(09:10) Seth は、Xcode 14 に新しい Instruments Game Memory テンプレートを導入しました。このテンプレートには、割り当て、メタル リソース イベント、VM トラッカー、仮想メモリ トレース、メタル アプリケーション、GPU などのインストゥルメントが含まれています。 CPU 割り当て、メタル リソース イベント、および VM フットプリントを同じタイムラインに配置します。

ゲームは多くの場合、起動フェーズ中にリソースを集中的に割り当てます。すでに実行中のプロセスにアタッチするのではなく、新しいゲームの起動からプロファイリングを開始することをお勧めします。この方法でのみ、スタートアップ期の成長曲線を確認できます。

(10:04) Xcode から [プロファイル] をクリックすることに加えて、次のこともできます。xctrace自動録画。

xctrace record --template "Game Memory" \
               --attach ModernRenderer \
               --output ModernRenderer.trace \
               --time-limit 30s

キーポイント:

  • xctrace record楽器の録音を開始します。 ---template "Game Memory"このセッションの説明用の新しいテンプレートを指定します。 ---attach ModernRendererModernRenderer という名前のプロセスにアタッチします。 ---output ModernRenderer.trace結果をトレース ファイルとして保存します。 ---time-limit 30s録画期間の制御は、ゲームプレイの起動期間や一定期間を自動プロセスで再現する場合に適しています。

(10:14) デバイスを指定したい場合は、次を追加できます--device-name

xctrace record --device-name "Seth's iPhone" \
               --template "Game Memory" \
               --attach ModernRenderer \
               --output ModernRenderer.trace \
               --time-limit 30s

キーポイント:

  • --device-nameターゲットデバイスとしてiPhone、iPad、またはApple TVを選択します。
  • 残りのパラメータは同じままであるため、異なるデバイスに対して同じプロファイリング コマンドのセットを簡単に使用できるようになります。
  • 生成されたトレースを繰り返し開いて、さまざまなリソース パッケージ、さまざまなレベル、またはさまざまな画質設定のメモリ増加を比較できます。

(10:23) 録画後、最初に割り当てを確認してください。ヒープ割り当て、匿名 VM、割り当てサイズ、オブジェクト参照数、割り当てスタックが表示されます。 Metal ゲームでは、通常、IOAcelerator は Metal リソースに対応し、IOSurface はドローアブルに対応します。 [割り当て] には、デフォルトで割り当てサイズが表示されます。また、[割り当て密度] に切り替えて、割り当てのピークがいつ発生するかを確認することもできます。

(12:50) 次に、メタル リソース イベントを見てください。ここでは、メタルリソースの割り当てとリリース履歴を確認できます。セッションでは、リソースに Metal API を介してラベルを付けることができるため、ツール内で特定のテクスチャやバッファを名前で識別できると特に言及しました。

(13:38) 最後に、VM トラッカーを見てみましょう。ダーティ サイズは圧縮されていないダーティ メモリ、スワップ サイズは圧縮またはスワップされたメモリです。これは、これらの割り当てのうちいくつがフットプリントに入るのかという質問に答えます。

3. メモリグラフをキャプチャし、現在の状態を逆アセンブルします。

(15:09) インストゥルメントはタイムラインの表示に適しています。メモリグラフは、ある瞬間の完全な状態を表示するのに適しています。オブジェクトの作成履歴、参照関係、圧縮およびスワップアウト情報が保存されます。問題の発生時に 1 回捕捉することも、問題の発生前後に 1 回捕捉して比較することもできます。

メモリ グラフを分析する前に、セッションは Malloc スタック ログをオンにすることを推奨します。後続のツールがオブジェクトのソースを追跡できるように、割り当て情報を記録します。

(16:52) Xcode から起動しない場合は、環境変数を使用して有効にすることができます。

# See `man malloc`.
MallocStackLogging=lite # Live allocations only.
MallocStackLogging=1    # All allocation and free history.

キーポイント:

  • MallocStackLogging=liteまだ有効な割り当てのみが記録されるため、コストが低くなります。 -MallocStackLogging=1すべての割り当てとリリースの履歴を記録し、断片化やその他の問題を確認するのに適しています。
  • セッション中、Jack は、ほとんどの人はライブ オブジェクトによって参照されるシーンのみを見るため、Live Allocation Only が推奨されると述べました。
  • 電源を入れた後、heapmalloc_historyなどのツール

(18:07) Mac ゲームが利用可能ですleaksコマンドラインでメモリグラフをキャプチャします。

leaks $PID     --outputGraph foo.memgraph
# or
leaks GameName --outputGraph foo.memgraph

キーポイント:

  • leaks $PIDプロセスIDによるキャプチャ。 -leaks GameNameプロセス名でキャプチャします。 ---outputGraph foo.memgraphスナップショットをメモリ グラフ ファイルとして保存します。
  • セッションは、この方法は SSH でリモートで実行でき、フルスクリーン ゲームでフォーカスを維持する必要があるシナリオに適していると述べました。

(18:41) メモリグラフを取得したら、最初に使用しますfootprint高レベルの分類を参照してください。 IOAccelerator、MALLOC_*、VM_ALLOCATE などのカテゴリがリストされます。ゲームが Unity またはカスタム アロケーターを使用する場合、匿名メモリの大きなチャンクがタグなしの VM_ALLOCATE として表示される場合があります。

(20:12) Apple プラットフォームでは、アプリケーションが最大 16 個のアプリ固有のタグを使用できます。カスタム アロケータは匿名マッピングにラベルを付けることができるため、後でツールで識別しやすくなります。

size_t length;

int tag = VM_MAKE_TAG(VM_MEMORY_APPLICATION_SPECIFIC_1); // Check out `man mmap`.
    
void * reservation = mmap(NULL,
                          length,
                          PROT_READ | PROT_WRITE,
                          MAP_ANONYMOUS | MAP_PRIVATE,
                          tag, // Instead of using default `-1`.
                          0);

if (reservation == MAP_FAILED) {
    @throw [[NSError alloc] initWithDomain:NSPOSIXErrorDomain
                                      code:errno
                                  userInfo:nil];    
}

return reservation;

キーポイント:

  • VM_MAKE_TAG(VM_MEMORY_APPLICATION_SPECIFIC_1)16 個のアプリケーション カスタム タグから VM タグを作成します。 -mmap(...)匿名のプライベート マッピングを作成します。
  • 5 番目のパラメータが使用されますtag、デフォルトを置き換えます-1
  • MAP_FAILEDブランチは POSIX エラーを次のように変換しますNSError、上位レベルの処理を容易にするため。
  • この方法で生成されたメモリ領域は、後続の分析ツールでより明確なカテゴリに表示されます。

(20:30) コードがmach_vm_allocate、同様のタグをフラグに含めることもできます。

size_t page_count;

mach_vm_size_t allocation_size = page_count * PAGE_SIZE;
mach_vm_address_t vm_address;
kern_return_t kr;

kr = mach_vm_allocate(mach_task_self(),
                      &vm_address,
                      allocation_size,
                      VM_FLAGS_ANYWHERE | VM_MAKE_TAG(VM_MEMORY_APPLICATION_SPECIFIC_1));
    
if (kr != KERN_SUCCESS) { // Refer to mach/kern_return.h.
    @throw [[NSError alloc] initWithDomain:NSMachErrorDomain
                                      code:kr
                                  userInfo:nil];
}
    
return vm_address;

キーポイント:

  • allocation_size = page_count * PAGE_SIZE割り当てサイズをページ単位で計算します。 -mach_task_self()現在のタスクを指します。 -VM_FLAGS_ANYWHEREシステムに適切なアドレスを選択させます。 -VM_MAKE_TAG(...)アプリ固有のタグをフラグに組み込みます。 -KERN_SUCCESS範囲外の戻り値はマッハエラーフィールドに変換されます。

(21:12) 次に使用しますvmmapダーティとスワップの分割を確認してください。footprintここでのダーティ サイズには、スワップされたサイズと圧縮されたサイズが含まれます。vmmapそれらを異なる列に分割します。スワップされた列は、最適化される可能性のある使用量の少ないメモリを指すことがよくあります。

(23:32) 次に使用しますheapmalloc オブジェクトを見てください。 Xcode14heapMalloc Stack Logging によって記録された情報を使用して呼び出し元または責任のあるライブラリを特定し、大規模な非オブジェクト メモリがどのライブラリまたはプラグインからのものかを特定するのに役立ちます。セッションの例では、Manifold Garden のメモリ グラフは、FMOD Studio と GameAssembly.dylib がかなりのヒープ メモリを占有していることを示しています。

(26:45) ラージ オブジェクトのアドレスを見つけたら、次を使用します。malloc_history割り当ての呼び出しスタックを確認します。 Xcode メモリ デバッガを使用して、インスペクタでオブジェクトの割り当て履歴を確認することもできます。

(27:48) 最後に参照関係を確認します。leaksメモリ グラフを使用して、参照ツリー、リーク、保持サイクルを確認できます。 Xcode 14 の再設計されたメモリ グラフ ビューは、選択したオブジェクトの入力エッジと出力エッジを表示し、複雑なゲーム状態でもオブジェクトがまだ生きている理由を理解するのに適した近傍選択ポップオーバーを提供します。

4. Metal Debugger を使用して GPU リソースを監査する

(30:37) メタル リソースは、ゲーム内でメモリの大きな部分を占有することがよくあります。 Seth の提案は、GPU フレーム キャプチャから Metal Debugger に入り、次に Memory Viewer を開くことです。

メモリ ビューアのテーブルでは、テクスチャのみを表示するなど、カテゴリ別にリソースをフィルタリングできます。最も役立つ列には次のようなものがあります。

  • インサイト: ツールによって検出されたメモリ最適化の推奨事項をリストします。
  • 割り当てられたサイズ: リソース サイズで並べ替え、最大のテクスチャ、バッファ、およびモデル データを最初に監査します。
  • 前回バインドされてからの時間: 長期間バインドされていないリソース、または一度も使用されていないリソースを検索します。
  • ピクセル形式: テクスチャをよりメモリを節約できる形式に変更できるかどうかを確認します。

(33:03) リソースが長期間使用されていない場合は、解放することを検討できます。将来使用する可能性がある場合は、揮発性のパージ可能な状態に設定できます。システム メモリの負荷が高い場合、Metal はそのようなリソースを削除する可能性があります。リソースが空になると、システムはそのリソースをゲームのフットプリントにカウントしなくなります。再利用する前に、コンテンツがまだ存在していることを確認し、必要に応じて再ロードする必要があります。

このロジックは擬似コードで表現できます。以下は公式の抜粋ではなく、制御フローのみを説明しています。

for each metal_resource in captured_resources:
    if metal_resource.was_never_bound:
        audit_asset_loading_path(metal_resource)
    else if metal_resource.has_not_been_bound_for_a_while:
        mark_as_volatile_or_release(metal_resource)
    when_resource_is_needed_again:
        if resource_content_is_empty:
            reload_resource_content()

キーポイント:

  • was_never_boundMemory Viewer で一度も使用されていないリソースについては、最初にロードする価値があるかどうかを確認してください。 -has_not_been_bound_for_a_whileTime Because Last Bound に対応する優れたリソース。 -mark_as_volatile_or_release2 つの戦略を示します。再構築可能なリソースは揮発性に設定され、不要になったリソースは直接解放されます。 -resource_content_is_emptyパージ可能状態が空になった後の回復パスに相当します。 -reload_resource_content()これはゲーム独自のリソース システムによって完了する必要があり、セッションは特定の API フラグメントを提供しません。

(34:18) テクスチャにはいくつかの明確な保存指示もあります。多くのテクスチャは 16 ビットの半精度ピクセル形式を使用できます。アルファのみが必要な場合は、複数のカラー チャネルを避けます。読み取り専用テクスチャでは、ASTC や BC などのブロック圧縮を考慮できます。 A15 Bionic 以降では、テクスチャとレンダー ターゲットでも非可逆圧縮を使用できるようになり、品質を可能な限り維持しながらメモリを削減できます。

(35:10) ストレージ モードもメモリに影響します。単一パスでのみ使用される一時的なレンダー ターゲットはメモリレスにすることができます。典型的な例は、深度、ステンシル、またはマルチサンプル テクスチャです。テクスチャは GPU によってのみ使用され、共有または管理されるよりもプライベートが優先されます。このセッションでは、iPhone や iPad と同様に、Apple シリコン Mac ではマネージド モードが必要ないことも思い出されました。

(36:05) 複数のリソースを同時に使用しない場合は、同じバッキング割り当てを共有するように、エイリアス化されたリソースをヒープから割り当てることができます。また、Seth は、こ​​れらのリソースへのアクセスの同期には細心の注意を払うように注意します。


重要ポイント

  • **メモリバジェット HUD を作成します。 ** 現在のフットプリント、ピーク時のフットプリント、開発バージョンのゲームで利用可能なメモリを表示します。実行する価値がある理由: Session では、Apple プラットフォームが実際のメモリ使用量と実行制限を理解するためのコアメトリクスがフットプリントであると明示的に述べています。開始方法: 使用するproc_pid_rusage()読むri_phys_footprintそしてri_lifetime_max_phys_footprint、サポートされているプラ​​ットフォームで使用os_proc_available_memory()利用可能なメモリを読み取ります。

  • **レベルのロードを記録可能なプロセスにします。 ** 典型的なレベルごとに 1 つのエントリを作成しますxctrace record --template "Game Memory"注文。実行する価値がある理由: ゲーム メモリ テンプレートは、割り当て、メタル リソース イベント、および VM トラッカーを同じタイムラインに配置します。これは、異なるリソース パッケージの起動中のメモリ増加を比較するのに適しています。開始方法: 起動、レベルに入ってロビーに戻るまでの 30 秒間のトレースを記録し、CI またはパフォーマンス テスト製品に保存します。

  • **カスタム アロケータの VM にタグを付けます。 ** 独自開発のエンジン、Unity プラグイン、またはリソース システムが匿名 VM を使用する場合、さまざまな用途をアプリ固有のタグにマッピングします。実行する価値がある理由: セッション Manifold Garden の例は、マークされていない VM_ALLOCATE が分析の明確さを低下させることを示しています。開始方法:mmapデフォルトへのパス-1と置き換えますVM_MAKE_TAG(...)mach_vm_allocateパスはタグをフラグにマージします。

  • **メモリグラフのトラブルシューティングマニュアルを作成します。 ** メモリのピークに遭遇したときに最初にキャッチします.memgraph、もう一度押しますfootprintvmmapheapmalloc_historyleaks注文を確認してください。実行する価値がある理由: これらのツールは、カテゴリ、ダーティ/スワップ分割、オブジェクト分散、割り当てスタック、参照関係という 5 つの質問にそれぞれ答えます。開始方法: 各コマンドの入力、出力、次の判定条件をチーム Wiki に記録します。

  • **メタルリソースの定期的な監査を実行します。 ** Metal Debugger Memory Viewer を使用して、各マイルストーンの割り当てサイズと最終境界からの時間を並べ替えて確認します。実行する価値がある理由: メタル リソースが Apple シリコン上のフットプリントに入り、テクスチャ フォーマット、ストレージ モード、およびパージ可能な状態はすべて実際のメモリに影響を与える可能性があります。開始方法: 最初に最大テクスチャを処理し、次に長期バインドされていないリソースを処理し、最後に一時的なレンダー ターゲットをメモリレスに変更できるかどうかを確認します。


関連セッション

  • Discover Metal 3 - Metal 3 の全体的な機能を紹介します。これは、Metal デバッガーとこの分野のリソース最適化の提案を理解するための入り口となります。
  • Metal 3 でバインドレス化 - ヒープとリソース管理の詳細な説明。このセッションでヒープ エイリアシングについて言及されている場合は、引き続き監視することをお勧めします。
  • MetalFX アップスケーリングによるパフォーマンスの向上 — MetalFX がどのようにレンダリング負荷を軽減するかについて説明します。これは、メモリ バジェットと併せて画質戦略を計画するのに適しています。
  • Metal 3 でリソースの読み込みを高速化 - リソースのストリーミング読み込みに注意してください。これは、このサイトの起動期間中のメモリ増加とリソース読み込み監査を補完するものです。
  • メタル レイ トレーシングのパフォーマンスを最大化する — メタル レイ トレーシングのパフォーマンスとリソース構成、および大規模シナリオに適したメモリ最適化の背景について説明します。

コメント

GitHub Issues · utterances