Skip to content
Go back

Performance and Memory Post Mortem for Middle-earth: Shadow of War

· Updated:

slides

拙訳

Threading

  • 同期プリミティブをカーネルのものからアトミックなスピンロックに変更
    • カーネルのものはコンテキストスイッチが望まれるところでは未だ使用する
  • multiple readers single writerのプリミティブを導入
    • 例えば、複数スレッドから物理シミュの状態を読み出すのに使われる
    • Microsoft系にはSlim Reader/Writer Locksプリミティブがある
    • Sony系にも同様のプリミティブがある
  • コンソールでは、CPUを半分に分けて、それぞれがゲームプレイまたはレンダリングを実行する
    • L2キャッシュが共有されていることを活用するため
    • それぞれが同じキャッシュラインに同時に触れないようにする必要がある
  • The Monolith Approach
    • ジョブの大きさはシングルスレッド的くらいの量を維持した
      • 新人エンジニアに対する参入障壁を下げる、並列化によるバグ混入を抑える、同期の全体数を少なくする、CPUキャッシュの利用効率が良くなる、などがある
    • システム全体をバックグラウンド・スレッドに移動
    • 大きなシステムにハード・アフィニティを設定
    • 低優先スレッドでCPUの隙間を埋める
      • ファイルIO、ストリーミング、非同期レイキャスト、など
  • パイプライン化
    • 処理は循環コマンドバッファの使用を通じてパイプライン化される
    • パイプラインは片方向に処理を追加する
    • これらコマンドバッファのそれぞれは専用のコアで動作する専用のスレッドで実行される
  • Dynamic Frame Pacing
    • GPUバウンドなときには、パイプラインの最初のステージの開始を少し遅延させる
      • パイプラインが間延びすることで入力レイテンシーが大きくなってしまうことを防ぐため
    • 詳細:
      • パイプラインにおける次のステージが1フレーム以上遅れないようにしないといけない
      • 理想的には、すべてのパイプライン化したステージは、数msの差異でもって、並列に動作する
      • これは、パイプライン全体が最初のステージによってboundされる場合には正しいと思われる
      • システム全体が最後のステージによってboundされる場合には、数フレーム前に計算されたフレームが表示されることになる
      • これは、パイプラインのtelescopingの性質によって引き起こされる
      • vsyncが有効のときは、常に、最後のステージによってboundされる

Renderer

  • 定数バッファ
    • 定数バッファを更新頻度別に分解した
    • レンダラでの定数へのアクセスは実際のバッファへのポインタを返す
    • ゲームへはアクセサを介して定数バッファを直接露出する
    • GPUに送ると、コピーが作られてdirty stateがクリアされる
    • frame codeがGPUでクリアされてからメモリを再利用する
    • マテリアルの定数バッファはアセットにベイクされる
    • 使う定数のみを割り当てることで、定数バッファを小さくする
      • ほとんど使わないものが最後尾に来るようソートされる
      • バッファ範囲外を読み出すと0を返す振る舞いを利用して、0となる定数を末尾に置く
  • 一般的な最適化
    • より高速なファーストパーティのAPIを使う
    • フレーム間の参照カウンタをすべて取り除く
    • frame codeのみで寿命を管理する
    • グラフィクスAPIのステート全体をキャッシュして、冗長なステート変更を取り除く
    • キャッシュラインを跨がないようにGPUメモリを割り立てることで、キャッシュのflushingをへらす
      • CPUアクセスではframe codeで追跡する
    • 動的なCPU負荷調整
      • メッシュ数をへらすため、CPU負荷に応じてLODをpushing outする
      • CPU使用率、memory pressure、物理ページのマッピングコストをへらすため、高ミップのストリーミングを停止する

Performance

  • メモリ
    • すべての割当を2MiBのページで行うようにした
      • TLBミスをへらすことで、64KiBページと比較して20%のパフォーマンスゲインに
    • プロセス生成時に事前割当を行うようにした
  • 最後
    • 開発ビルド
      • イテレーションを改善するため、DLLを使用
      • インクリメンタル・リンクを有効化
      • Debug:FastLinkが使用可能
    • リテールビルド
      • DLLをLIBでコンパイルする
      • インクリメンタル・リンクを無効化
      • LTCGを有効化
      • PGOを有効化
      • PGOを阻害するため、Microsoft系ではCOMDAT foldingを無効化

Memory

  • GPUメモリとして、大ページサイズより小さい64KiBページを使う
    • 再利用性が高まるが、TLBミスが増える
  • Mipmap Streaming
    • 固定のメモリプールを使ってMIPMAPを出し入れする
      • 前作では、MIPMAPは読み込んだっきりでアンロードしていなかった
    • メッシュの大きさからMIPMAP値を近似する
      • 事前に、最大テクセル密度を持つ最大のトライアングルを特定しておく
      • 実行時に、そのトライアングルをスクリーン空間に投影してMIP値を計算する
    • フレーム情報によって絞る
      • フレームごとのメッシュ/マテリアルの数、メッシュごとのframe code、thrashing回避のためのdampener、を使う
    • Mipmap解析のCPUコストはフレームあたり0.1msで固定
    • 64KiBページのプールを使って、高MIPを格納する
      • プールが枯渇するまで高MIPがロードされる
      • 高MIPストリーミングをサポートするテクスチャは高MIPがメモリに割当られないまま生成される
  • Texture2DArray
    • Texture2DArray内のスライスはすべて同レベルでサンプルされるため、ブレンディングで有効
    • 複数のTexture2Dを使うより分岐を回避しやすい
    • AMDではMIPごとスライスごとに格納しなければならないため、スライス数を2の累乗に切り詰める必要がある
      • GPUから実際に読み出されるテクスチャ部分にページを手動でバインドすることで対処する
    • 内容が共通しても、配置の都合でリソースごとに複製されることがある
      • 物理メモリページを共有することで対処
    • 64KiBより小さいMIPはページを共有して詰め込む