拙訳
- UMA:CPUとGPUが同じ物理メモリを共有するアーキテクチャ
- CPUとGPUで同じ領域を直接読み書きできる
- NUMAとくらべてメモリ帯域幅が一桁くらい狭い
- DDRでは数十GB/sなのに対し、GDDRでは数百GB/s
- NUMA:GPUが専用の物理メモリを持つアーキテクチャ
- UMAとくらべてメモリ帯域幅が広い
- 外とデータをやり取りするためのPCIeバスの帯域幅が狭い
- PCIe 3.0では16GB/s、PCIe 4.0では32GB/s
- CPUはBAR(ベース・アドレス・レジスタ)によりVRAMに直接書き込みできる
- BARでは256MBまでに制限される
- ReBAR(Resizable BAR)では全体にアクセスできる
- write combining:
- CPUキャッシュを介さずに連続した書き込みをまとめて行う機能
- 「CPU書き込み・GPU読み取り」を行うときに有効
- CPU読み取りを行わないので、キャッシュする必要がない
D3D12
- ヒープタイプ:
DEFAULT:GPUでアクセスUPLOAD:CPUからGPUへREADBACK:GPUからCPUへCUSTOM:- UMA
- L0(SysRAM)
NOT_AVAILABLE:CPU読み書き不可WRITE_COMBINE:キャッシュされないメモリアクセス- CPU書き込みが速く、CPU読み取りが非常に遅い
WRITE_BACK:キャッシュされるメモリアクセス- CPU読み取りが速い
CacheCoherentUMA == TRUEなら、書き込みも速いCacheCoherentUMA == FALSEなら、WRITE_COMBINEのほうが速くなるかも
- L0(SysRAM)
- NUMA
- L0(SysRAM):GPU読み取りはPCIeを介するため比較的遅い
NOT_AVAILABLE:CPU読み書き不可。不要WRITE_COMBINE:キャッシュされないメモリアクセス- CPU書き込みが速く、CPU読み取りが非常に遅い
WRITE_BACK:キャッシュされるメモリアクセス- CPU読み書きが速い
- CPU読み取りを行わないなら、キャッシュ汚染しない
WRITE_COMBINEのほうが良い
- CPU読み取りを行わないなら、キャッシュ汚染しない
- CPU読み書きが速い
- L1(VRAM)
NOT_AVAILABLE:- CPU読み書き不可
- GPUのみで読み書きする場合に最適
WRITE_COMBINE:無効WRITE_BACK:無効
- L0(SysRAM):GPU読み取りはPCIeを介するため比較的遅い
- UMA
- CPUアクセス可能なVRAMはAPI上では公開されていない
- メーカーの拡張APIを使うとできる場合もある
- ドライバが自動でやってくれる場合もある
- VRAMが足りなくなると、OSはリソースをVRAMからSysRAMへ移動させることができる
- VRAMに残す優先順位を付けるためのAPIがある
テクスチャ
- テクスチャはGPU固有のswizzleパターンで格納される
- アプリはそのパターンを知らないので、非swizzleなデータをドライバ経由でコピーする必要がある
- UPLOADバッファからDEFAULTテクスチャへコピーする
- CPU書き込み可能なテクスチャへWriteToSubresourceで書き込む
- テクスチャがSysRAMに配置される場合、NUMAでパフォーマンス上の問題になる可能性がある
- UMAやBARでは選択肢としてありだが、コピー処理でCPU負荷が高くなるかもしれない
- アプリはそのパターンを知らないので、非swizzleなデータをドライバ経由でコピーする必要がある
バッファ
- 何度も再利用しない小さなデータなら、UPLOADバッファのパフォーマンス損失は無視できる
- 逆に、DEFAULTにコピーすると、そのコピーの分だけロスになる可能性もある
- UMAやReBARでは、常にUPLOADバッファを使うこともできるようになるかも
コピーキュー
- NUMAでは専用のDMAユニットで処理される
- 計算処理に対して非同期に行うことができる
- UMAではDMAがないので、他のキューのコマンドと共に単一のハードウェアキューでフラット化される
- UMAに最適化するなら、CPU書き込み可能なテクスチャを使用すると良い
- NVIDIAやAMDのGPUでは、コピーキューを2つ持つことができる
- 優先度の高い処理を別にすることで、DIRECTキューへの影響を小さくする
- 設計の例:
- リングバッファでステージングバッファを管理する、とか
- レンダリングフレームと同期してコマンド記録する、とか
- 独立したスレッドでコマンド記録する、とか
まとめ
- NUMAの場合:
- CPUからアクセスする必要ないリソースは必ずVRAMに置く
- コピーキューを使って、VRAMに置いたリソースを非同期に初期化する
- 大きなバッファはステージングバッファ経由でコピーキューを使って更新する
- ストリーミング用のコピーキューとフレームリソース用のコピーキューを用意する
- 可能であれば、大きなバッファはCPU書込み可能なVRAMを割り当てる
- UMAの場合:
- コピーキューを使わず、CPU書き込みできるバッファを使う
- 共通:
- キャッシュされないCPU読み取りは行わない
- CPUからGPUへのメモリの更新では、memcpyのようなシーケンシャルな書き込みで行う