DirectStorage の概要

はじめに

Xbox Series X|S コンソール専用の DirectStorage API の概要を説明します。 デスクトップの DirectStorage の詳細については、「デスクトップの DirectStorage」を参照してください。

PCIe バスを使用して接続された最新の NVMe ストレージ デバイスは、非常に高いレベルのスループットと IOPS (1 秒あたりの I/O 要求数) を達成できます。 Win32 API のオーバーヘッドは、使用可能なストレージ帯域幅を利用できる場合でも、それを活用すると CPU 使用率が許容できないほど高くなる可能性があることを意味します。 これは、ワークロードが多数の小さな要求で構成されている場合に特に当てはまります。

DirectStorage API は、基盤となる NVMe ハードウェアと密接にやり取りすることで、オペレーティング システムのオーバーヘッドの大半を除去するように設計されています。 これにより、CPU 使用率を低く抑えて帯域幅を高くすることができます。 目標は、1 つの CPU コアの最大 10% を使用して、1 秒あたり最大 5 万の要求を処理できるようにすることです。

既知の問題

本体の世代が進むごとに、より高い解像度のアセットへのニーズが高まるにつれて、ゲーム コンテンツのサイズはますます大きくなっています。 既存の Xbox One ハードウェアおよびソフトウェアにはいくつかの制限事項があり、それによってデベロッパーがこの次世代コンテンツ用にハード ドライブからデータをメモリに取得する能力が妨げられています。

  • CPU 使用率の高さ

    • 既存の Win32 API ではオーバーヘッドで CPU コア全体が必要になる場合があります。
    • これは、タイトルからの要求の数に基づいています。
  • ディスクからの最大帯域幅の不足

  • ディスク要求を優先順位付けできない

    • タイトル要求の優先順位を設定できない場合、応答性の高いストリーミング システムを作成することは困難な場合があります。
  • ディスク要求をキャンセルできない

    • 要求をキャンセルできない場合、投機的な読み取りシステムを作成するのは困難な場合があります。
  • ハードウェアの高速化による展開が行われない

    • ハードウェアの高速化による展開を行わないと、ソフトウェアで展開を実行するために大量の CPU リソースが必要になる場合があります。

DirectStorage API セットは、こうした問題に直接対処します。 全体的な効果としては、Xbox ファイル システムのパフォーマンスが大幅に向上しています。

CPU 使用率

DirectStorage の主な設計目標は、タイトルが 50K IOPS を維持し、1つの CPU コアの 5% ~ 10% のみを使用できるようにすることです。 これにより、タイトルは NVMe ストレージ サブシステムから最大の帯域幅を得ることができると同時に、CPU を他のタイトル要件に使用できるようになります。

DirectStorage ではハードウェア展開のサポートも追加されます。 各読み取り要求は、NVMe ドライブから組み込みのハードウェア展開ブロックに直接ルーティングできます。 これにより、展開時にタイトルが CPU リソースを消費する必要がなくなります。

キューに登録されたパイプライン モデル

DirectStorage ではバッチ方式を使用しており、複数の要求がキューに追加されます。 キューは後で、次のパイプライン ステージにフラッシュされます。 これにより、パイプライン ステージ間の移行のための全体的な CPU コストがすぐに削減されます。 既存の Win32 API セットでは、要求ごとに移行があります。 DirectStorage キューはロックフリー アルゴリズムを使用して競合を最小限に抑えます。 タイトルは各キューがフラッシュされるタイミングを制御できます。

多くの場合、Win32 API を使用すると、ディスクのデータを別のバッファーにコピーしなければならないことがあります。 場合によっては、データを複数回コピーする必要があります。 DirectStorage は、タイトルで指定されたコピー先バッファーを各パイプライン レイヤーに直接マッピングすることで、この問題を解決します。 ハードウェアは、タイトルによって提供されるバッファーに直接書き込みを行います。

こうした変更は、CPU オーバーヘッドを大幅に削減するために役立ちます。

展開

ハードウェアがデータを展開する機能が向上しました。 これにより、NVMe サブシステムから提供されるデータよりも幅広い形式をより高速に処理できるようになっています。 さらに DirectStorage では、インプレース展開がサポートされているため、圧縮データ用と展開データ用に別個のバッファーを管理する必要がありません。

ハードウェアは BCPACK、DEFLATE をサポートしており、最終的なコンテンツをスウィズルする機能が提供されます。 これらの形式は相互に排他的ではありません。 3 つすべてをデータに適用できます。 これにより、最適な圧縮率とパフォーマンスを提供する方法をタイトルが選択できるようになります。 異なるアセットでは、異なる圧縮やスウィズルの設定を使用できます。

キュー深度

以前の推奨事項は、回転ドライブについて、処理中の非同期要求を一度に 12–16 件のみ維持することでした。 それ以上数を増やしてもパフォーマンスは向上せず、それより減らすとパフォーマンスが大幅に低下していました。 これにより、タイトルは未処理の読み取り要求を推奨目標内に維持するための追加作業を行う必要がありました。

タイトルが 50,000 IOPS を達成できるようにするという DirectStorage の目標のため、推奨事項が変更されました。 タイトルは、未処理の作業とキューの深さのバランスを取る必要がなくなりました。 タイトルは、未処理の要求をすべて送信する必要があります。 一部の要求を保留しておいても利点はありません。 多くの場合、要求を保留すると、ハードウェアは新しい要求を待機する間ストールするため、パフォーマンスが低下する可能性があります。

場合によっては、オペレーティング システムは引き続き、大規模な読み取り要求を複数の小さな要求に分割する (たとえば、ディスクの断片化を処理する) ことが必要になります。 ただし、DirectStorage のアーキテクチャではこれが考慮されました。 50,000 IOPS の設計目標は、IO 操作のタイトル数に基づいており、ハードウェアに送られる最終的な要求に基づくものではありません。

通知

Win32 アーキテクチャでは、読み取り完了の通知に多大なオーバーヘッドが消費されます。 タイトルは、OVERLAPPED 構造体をポーリングするか、関連付けられた Event ハンドルを待機するか、または同期ブロッキング読み取りを実行できます。 全体として、これにより各読み取り要求のリソース要求が増加します。

DirectStorage では、通知の 2 つの非同期概念を保持しながら、3 つ目のメソッドも追加しています。 DirectStorage では同期ブロッキング読み取りはサポートされておらず、タイトルが独自のシステムを実装することができますが、推奨はされません。

1 番目の非同期メソッドは、関連付けられた要求が完了したときに設定されるステータス ブロックによって実装されます。 タイトルは必要に応じてブロックをポーリングし、読み取りが完了したかどうかを判断できます。 これは、 完了のための OVERLAPPED 構造体をポーリングする Win32 メソッドに似ています。

2 番目の非同期メソッドは Windows イベント オブジェクトを使用して完了をシグナルすることです。 これは、対応する Event オブジェクトで OVERLAPPED 構造体を使用する場合と似ています。 タイトルは WaitForSingleObject メソッドを使用して、読み取り操作が完了するまで呼び出し元のスレッドを中断させることができます。

3 つ目の非同期メソッドは、ID3D12Fence を使用して実装されます。 タイトルは一時停止してフェンスを待機するか、必要に応じてフェンスをポーリングすることができます。 また、GPU がフェンスを利用して、完了した要求の通知を直接行えるという利点もあります。

DirectStorage 通知システムは、単一の読み取り要求にはバインドされていません。 これは、先行するすべての読み取り要求が完了したときに通知される、キュー内に配置されたエントリです。 これにより、タイトルは通知にどの程度の粒度を望むかを制御できます。 通知は常にキューの順序で行われます。 キューは FIFO (先入れ先出し) キューと見なすことができます。 タイトルでは、関連する最後の通知のみをクエリする必要があります。 以前にキューに入れられた要求は、すべて完了していることが保証されます。

メモリ間での展開

DirectStorage は、ディスク ファイルではなくメモリである展開ソースを使用して展開ハードウェアを呼び出すキューの種類を提供します。 これにより、圧縮されたアセットがファイルをソースとしていない場合、または以前はファイルをソースとしていたがキャッシュとしてメモリに保存されていた場合に、展開を利用できます。 メモリをソースとするキューは、メモリをソースとする要求のみを受け入れ、ファイルをソースとするキューは、ファイルをソースとする要求のみを受け入れます。

メモリをソースとする要求で展開オプションが指定されなかった場合、展開ハードウェアは DMA コピー エンジンとしても機能できます。

DirectStorage は、完了通知が順序どおりであることは保証しますが、要求の処理が開始されるタイミングについては保証しません。 そのため、保留中の要求間にデータの依存関係がないようにする必要があります。 つまり、要求 A の完了後に要求 B がエンキューされない限り、要求 A の宛先を要求 B のソースとして使用することはできません。

メモリをソースとするキューは、リアルタイムの優先度を使用して作成する必要があります。 また、メモリをソースとするリアルタイムの要求は、圧縮を解除する必要がある、ディスクをソースとする要求よりも前に、展開ハードウェアで処理されます。 ディスクをソースとするキューに展開要求がない場合、2 種類のキューは、もう一方の種類に影響を与えることなく、完全に並行して処理されます。

優先度

DirectStorage ではキューごとに優先順位レベルを割り当てることができます。 キュー内の各エントリは、キューの優先順位を継承します。 リアルタイム、高、正常、低の 4 つの異なる優先順位レベルが提供されます。 要求は、優先度に基づいて加重ラウンドロビン方式で処理されます。 たとえば、1 つの要求を通常の優先度で処理する前に、X 要求を高い優先度で処理します。 優先度が通常の要求 Y 件は、優先度が低い 1 件の要求を処理する前に処理します。

優先度の重み付けは、各要求のサイズに対してカウントされます。 各優先順位間の既定の重み付けは約 10 倍です。 これは、1 KB の低優先度要求ごとに、10 KB の中優先度要求と 100 KB の高優先度要求が処理されたことを意味します。

既存の Win32 読み取り要求は、同じ優先度システムを通してルーティングされます。 Win32 の要求はすべて、通常の優先度とみなされます。

メモリをソースとするキューは、リアルタイムの優先度を使用して作成する必要があります。

キャンセル

各 DirectStorage 読み取り要求には、タイトルが提供する 64 ビット マスクが関連付けられています。 これは、保留中の読み取り要求のキャンセルをサポートするためです。 タイトルは、マスク内の特定のフラグ セットに一致する要求をキャンセルできます。

キャンセルはサポートされていますが、それでも読み取り要求はハードウェアで処理できます。 タイトルのキャンセル要求は、ベスト エフォート型の試行になります。 要求がすでにハードウェアによってアクティブに処理されている場合、キャンセルすることはできません。

取り消し要求はベスト エフォートであるため、タイトルは読み取り要求の処理が完了したことが通知されるまで待機する必要があります。 タイトルは、キュー内の後の通知を受信するまで、必要なリソースを解放できません。 ただしこの間、以前のキャンセル要求で使用されたフラグと一致する新しい要求をキューに入れることは可能で、それがキャンセルされることはありません。

キャンセルされた要求が完了すると、キャンセルされて完全な結果が生成されなかった場合でも、成功と見なされます。 つまり、要求でキャンセルが試行された場合、タイトルは、完了時にキャンセルされた可能性のある要求の結果を使用できなくなります。

保証

Xbox One と Xbox One S の本体は、最小限として 40 MB/s を保証していました。 Xbox One X 本体では、この最小限保証が 60 MB/s に拡大されました。 これらの数値は実際のハードウェア制限よりもかなり低く、実際は 130 MB/s の範囲になります。 これはすべて、オペレーティング システムによって発生するオーバーヘッドが原因となっていました。

DirectStorage では、オペレーティング システムによって発生するオーバーヘッドのほとんどが排除されます。 これにより、ハードウェアの制限に近い最小限保証が可能になります。 新しい最小パフォーマンス保証は、生データで 250 ミリ秒あたり 2.0 GB/s です。 コンテンツの展開を利用すると、最終的な帯域幅が高くなります。

将来の Xbox 本体では、ユーザーによる動的インストールが可能な NVMe ベースのドライブの追加がサポートされます。 ユーザーがインストール可能なドライブについても、内部ドライブに関するものと同じ最小パフォーマンス保証が提供されます。

API の概要

DirectStorage インターフェイスは、Direct3D インターフェイスと同じパターンに従います。 タイトルは最初にシングルトン ファクトリを取得します。 このファクトリは、要求キューを作成してファイルを開くために使用されます。これらの各オブジェクトには、ハードウェアに対する直接のマッピングがあります。 個々の要求はその後、ハードウェアに送信されるキューに入れられます。

IDStorageFactoryX

IDStorageFactoryX は、キューを作成し、ファイルを開き、保留中の要求を送信するためのメインのインターフェイスです。

IDStorageFactoryX オブジェクトには以下のメソッドが含まれています。

  • OpenFile

    • 1 つのファイルを表す IDStorageFileX オブジェクトを作成します。
  • CreateQueue

    • IDStorageQueueX オブジェクトを作成します。 読み取り要求を作成するために使用されます。
  • CreateStatusArray

    • 完了ステータス フラグを管理する IDStorageStatusArray オブジェクトを作成します。
  • SetCPUAffinity

    • DirectStorage の呼び出しスレッド外の作業を、タイトルで定義された一連の CPU コアに制限します。
    • DirectStorageは、呼び出し元のスレッドでほとんどの作業を実行しようとします。 呼び出しスレッド外の作業は、呼び出しスレッドで実行できないときにのみ発生します。 次に例を示します。
      • 基になるリソース パイプラインは、IDStorageQueueX::Submit 実行中にいっぱいになるため、キュー内のすべての要求をプッシュできるわけではありません。 残りの要求は、リソースの解放後に処理され、DirectStorage ワーカー スレッドで実行されます。
      • ID3DFence または IDStorageStatusArray への要求完了の処理。
  • SetDebugFlags

    • デバッグを支援するために、要求のエンキュー時に DirectStorage が追加の検証を行うかどうかを制御します。
  • SetStagingBufferSize

    • ストレージ デバイスから読み込まれたコンテンツを、復号化/圧縮解除する前に、一時的に格納するために使用されるステージング バッファーのサイズを設定します。 メモリ ソース キューのみが使用される場合、ステージング バッファーのサイズは 0 になります。

IDStorageFactoryX1

IDStorageFactoryX1 インターフェースは、GetStats メソッドによって IDStorageFactoryX インターフェースを拡張します。

  • GetStats
    • DirectStorage の統計を取得します。 この関数は、既存の診断およびテレメトリ パイプラインを DirectStorage に統合するために使用できます。 最小限の処理を行うため、頻繁に呼び出すことができます。 この統計には、Win32 ファイルの IO 操作は含まれていません。

IDStorageFileX

すべてのファイルはまず、IDStorageFactoryX オブジェクトを介して、DirectStorage で開く必要があります。 これは、Win32 API インターフェイスで CreateFile を使用するのと同じです。

ファイルは FILE_SHARED_READ アクセス許可を使用して開きます。 適切なアクセス許可が尊重されていれば、タイトルは必要に応じて、Win32 API を使用してファイルを同時に開くことができます。 開発時には、ルース配置とパッケージ化された配置の両方がサポートされます。

ファイルは、ファイル オブジェクトで Close 関数を明示的に呼び出して閉じるほか、一致する IDStorageFileX オブジェクトへの最後の参照が解放されたときにも閉じられます。 ただし、未処理の I/O 操作はすべて完了してからでないと、ファイルを閉じることができません。 つまり、ファイルを閉じるメソッドは、そのファイルに対する未処理の I/O 操作がすべて完了するまでブロックされます。

ゲームでは、GetHandle 関数を呼び出すことによって、IDStorageFileX オブジェクトで表されるファイルへの win32 ハンドルを取得します。 ハンドルは、GENERIC_READ アクセス許可と共有モード FILE_SHARE_READ で開かれます。 ファイルのサイズなどを問い合わせに使用できます。不要になった場合、ハンドルは CloseHandle() で閉じる必要があります。

IDStorageQueueX

読み取り要求は、IDStorageQueueX オブジェクトを通して NVMe に送信されます。 ただし、タイトルがキューで [送信] を呼び出すか、またはいずれかの Enqueue メソッドが、前回の送信以降にキューの容量の半分以上を満たし、自動送信をトリガーするまで、要求はデバイスに送信されません。 送信はパイプライン内の次のステージへの 1 つの移行として処理されます。 これによりタイトルは、タイトルとカーネル間の移行に CPU コストが発生するタイミングを制御できます。

IDStorageQueueX オブジェクトには、次の 4 つのプロパティがあります。

  • SourceType

    • キューが、ファイルをソースとする要求またはメモリをソースとする要求を受信できるかどうかを指定します。
  • 優先順位

    • キューに送信されるすべての要求の優先度 (リアルタイム、高、通常、低)。
    • メモリをソースとするキューは、リアルタイムの優先度を使用して作成する必要があります。
    • 要求は、優先度に基づいて加重ラウンドロビン方式の順序で処理されます。
    • Win32 要求は通常の優先度で処理されます。
  • Capacity

    • キューが保持できる未処理の要求の最大数。
    • キューの容量がいっぱいのときに要求をキューに入れようとすると、ハードウェアがエントリを完了するまでブロックされます。
    • キューに必要なメモリの量はおおよそ、キューの容量と DSTORAGE_REQUEST のサイズを掛け合わせたものになります。
  • 名前

    • これはあくまでデバッグに役立つものです。 この名前は、DirectStorage コードでは使用されませんが、開発者用ツール (PIX (NDA トピック)認可が必須です など) で表示されます。

ハードウェアは要求を非同期的に処理して、最高のスループットを実現します。 ただし Win32 と異なり、タイトルは FIFO 順序で完了を通知されます。 完了の通知により、同じキューに対する以前の要求もすべて完了したことが保証されます。

IDStorageQueueX1

IDStorageQueueX1 インターフェイスは、EnqueueSetEvent メソッドを使用して IDStorageQueueX インターフェイスを拡張します。

EnqueueRequest

このインターフェイスは、機能的には Win32 ReadFile インターフェイスと同じです。 個々の読み取り要求が作成され、キューに送信されます。 主な違いは、DirectStorage では、送信前に多くの要求をキューに入れることができ、ハードウェア展開がサポートされ、キャンセルもサポートされることです。

要求にはいくつかの主なプロパティがあります。

要求のソース Options.SourceType と Options.SourceIsPhysicalPages の組み合わせに応じて、DirectStorage は次の 3 つのプロパティ グループのいずれかを使用して、ソース データが存在する場所を指定します。

  • FileFileOffset

    • このグループは、Options.SourceType が DSTORAGE_REQUEST_SOURCE_FILE である場合に使用されます
    • File は以前に IDStorageFactoryX::OpenFile で開かれています。
    • FileOffset は、展開が使用される場合は 16 バイトにアライメントされている必要があります。展開が使用されない場合、アラインメント要件はありません。
      • これは、非同期読み取りのためにファイル内で 4 KiB アライメントが必要となっていた Win32 から大きく変更された点です。
  • Source

    • このグループは、Options.SourceType が DSTORAGE_REQUEST_SOURCE_MEMORY であり、Options.SourceIsPhysicalPages が FALSE である場合に使用されます。
    • 展開されるデータを保持するメモリ バッファー。
  • SourcePageArraySourcePageOffset

    • このグループは、Options.SourceType が DSTORAGE_REQUEST_SOURCE_MEMORY であり、Options.SourceIsPhysicalPages が TRUE である場合に使用されます。
    • Source と似ていますが、64 KB の物理ページの配列の形式でソース メモリ バッファーを提供し、最初のページにバイト オフセットを提供します。
    • 物理的な 64 KB のページは、XMemAllocatePhysicalPages によって割り当てることができます。

SourceSize

  • メモリ バッファーまたはファイルから読み取るソース データのサイズ (バイト単位)。

IntermediateSize

  • この要求で zlib と BCPACK の両方の展開が有効になっている場合は、IntermediateSize を使用して、ソース データの zlib 展開後 (および BCPACK 展開前) の中間サイズを指定します。
  • それ以外の場合は 0 に設定する必要があります。

要求の宛先 Options.DestinationIsPhysicalPages に応じて、DirectStorage は次の 2 つのプロパティ グループのいずれかを使用して、宛先が存在する場所を指定します。

  • Destination

    • このグループは、Options.DestinationIsPhysicalPages が FALSE である場合に使用されます。
    • 最終的に読み込まれたデータの宛先バッファー。
    • 展開は共有内部バッファーを使用して行われ、インプレースと見なすことができます。
  • DestinationPageArrayDestinationPageOffset

    • このグループは、Options.DestinationIsPhysicalPages が TRUE である場合に使用されます。
    • Destination と似ていますが、64 KB の物理ページの配列の形式で宛先メモリ バッファーを提供し、最初のページにバイト オフセットを提供します。
    • 物理的な 64 KB のページは、XMemAllocatePhysicalPages によって割り当てることができます。

DestinationSize

  • 最終的に読み込まれたコンテンツの予想サイズ (バイト単位)。 目的地には、操作に対応するのに十分なスペースが含まれている必要があります。
  • サイズは、展開が使用されていない場合は SourceSize に等しく、展開が使用されている場合は SourceSize より大きい必要があります。

CancellationTag

  • タイトルによって定義される任意の 64 ビット タグ。
  • このタグはキャンセル要求のマスクとして使用されます。

名前

  • デバッグを支援する省略可能な文字列。 Name は、PIX (NDA トピック)認可が必須です などの開発者ツール、または IDStorageQueueX::RetrieveErrorRecord から取得したエラー レコードに表示できます。 Name の文字列は、要求の有効期間全体でアクセス可能である必要があります。

Options

  • ZlibDecompress
    • RFC 1950 展開標準を使用してデータを展開する必要があることを示します。
  • BcpackMode
    • データの展開にどの BCPACK モードを使用すべきかを示します。
    • [なし] は有効なオプションであり、データが BCPACK 圧縮されていないことを意味します。
  • SwizzleMode
    • メモリ内で最終データをどのようにスウィズルするべきかを示します。 現在のリリースでは、DSTORAGE_SWIZZLE_MODE_NONE である必要があります。
  • DestinationIsPhysicalPages
    • Destination の代わりに DestinationPageArray および DestinationPageOffset を使用して宛先バッファーが指定されていることを示します。
  • SourceType
    • 要求は、メモリをソースとする場合 (そのため Source / SourcePageArray および SourcePageOffset プロパティを持つ) と、ファイルをソースとする場合 (そのため File / FileOffset プロパティを持つ) があります。
  • SourceIsPhysicalPages
    • Source の代わりに SourcePageArraySourcePageOffset を使用してソース バッファーが指定されていることを示します。

EnqueueStatus/EnqueueSignal/EnqueueSetEvent

要求はキューに入れ、一連の関連する要求として扱うことができます。 これを行うには、処理がキュー内の特定のポイントに達したときに、通知のためのエンキューを行います。 通知は前の読み取り要求がすべて完了したときにのみ処理されます。 これにより、前のすべての要求のデータをすぐに利用できるようになります。

タイトルには、2 つのポーリング メソッドと、通知のための 1 つの待機メソッドがあります。 タイトルは ID3D12Fence オブジェクトまたは IDStorageStatusArrayX オブジェクト、またはセット イベント操作を挿入できます。 ID3D12Fence は ID3D12Fence オブジェクトに対し、予想されたとおりに動作します。 タイトル スレッドは Event を待機し、CPU はフェンスをポーリングし、GPU はフェンスをポーリングすることができます。 IDStorageStatusArrayX オブジェクトは、CPU による完了のポーリングと、発生しうる読み取りエラーへのアクセスを許可します。 EnqueueSetEvent メソッドを使用すると、タイトル スレッドはポーリングではなく、指定されたイベントを待機できます。 これは ID3D12Fence::SetEventOnCompletion とは異なります。ID3D12Fence::SetEventOnCompletion の Xbox 実装は、シグナルが送信されるまでフェンス上でスピンするため、シグナルまで CPU ハードウェア スレッドを消費します。一方、EnqueueSetEvent では、タイトル スレッドは WaitForSingleObject/WaitForMultipleObjects を使用して、イベントがシグナルされるまで他のスレッドに CPU を生成できます。

前述したように、要求はすべて順番に完了し、これは基盤となるハードウェアがパフォーマンス向上のため要求の順序変更を決定した場合でも変わりません。 キューに登録されている以前の要求がすべて完了するまで、通知は行われません。

展開

展開は専用のハードウェアで処理されます。 これにより、従来の展開アルゴリズムから CPU オーバーヘッドが排除されます。 DirectStorage は初期化時にメモリの固定ブロックを割り当て、展開用の作業バッファーとして使用します。 これによってインプレース展開が可能になり、圧縮データと展開データを同時にメモリに保持する必要がなくなります。

展開ハードウェアは 3 つの操作モードをサポートしています。 これらのモードは相互に排他的ではないため、任意のモードの組み合わせを指定できます。 展開モードは、DEFLATE、BCPACK、スウィズルの順序で適用されます。

  • ZLibDecompress

  • BCPack

    • BCPack は、BCn データ専用に設計されたカスタムのエントロピー コーダーです。 これは一般に、カラー エンドポイントがパレット インデックス (すなわち重み) から分離され、rANS アルゴリズムを使用して圧縮されることを意味します。
  • Swizzle

    • スウィズル モードとシャッフル モードは、コンテンツ パイプラインで追加の最適化を提供できます。

高エントロピー データが圧縮される場合、圧縮により実際にサイズが大きくなる可能性があります。 逆に、対応する展開のサイズは小さくなります。 DirectStorage では圧縮の縮小は許可されていません。また、タイトルが、圧縮できない高エントロピー データを検出し、そのようなアセットでの圧縮を回避します。 詳細については、『DirectStorage と XBTC (NDA トピック) を使用した圧縮コンテンツの最適化認可が必須です』ガイドを参照してください。

ステージング バッファ

DirectStorage は、暗号化解除や圧縮解除などの操作を実行する前に、内部でバッファを使用して、生の NVMe ストレージから読み取られたすべてのコンテンツをステージングします。 このステージング バッファにより、NVMe ドライブと復号化/圧縮解除シリコンがパイプラインで並列に動作します。 これは、デフォルトで 32MiB で、最初の DirectStorage ファクトリ ポインタが取得されるときに割り当てられます。

タイトルが DirectStorage を使用してメモリからメモリへの圧縮解除操作のみを行う場合、ステージング バッファーは必要ありません。そして、SetStagingBufferSize を 0 に設定するために呼び出すことができます。 SetStagingBufferSize は、IDStorageQueueX オブジェクトも IDStorageFileX オブジェクトも存在しない場合にのみ呼び出すことができます。

DirectStorage の現在のリリースでは、0MiB または 32MiB のステージング バッファ サイズのみがサポートされています。

CancelRequestsWithTag

DirectStorage では、要求を取り消すことができます。 それぞれの要求には、タイトルで定義された 64 ビット タグが関連付けられています。 目的は、どの要求を取り消すかのビットマスクとして機能することです。 タイトルは取り消しのためのマスクと値を提供します。 キューは、tag & mask == value という条件に一致するすべての要求をキャンセルしようとします。

取り消しはベストエフォート操作で行われます。 要求がパイプライン内のどこにあるかによっては、取り消すことができない場合もあります。 たとえば、要求がハードウェアによって展開されている最中であれば、取り消しはできません。 API はすぐに戻り、キャンセルされたすべての要求の処理を待って動作を停止することはありません。 タイトルはキュー内の後の通知が発行されるまで待機してから、取り消された要求に関連付けられているリソースを解放する必要があります。

CancelRequestsWithTag を呼び出しているときに、同時に要求をキューに追加しないよう注意する必要があります。 この場合の動作は定義されていません。 ただし、CancelRequestsWithTag への呼び出しが返された後にキューに追加された要求は、条件が満たされた場合も取り消されません。 以前キューに入れられた要求だけが取り消されます。

GetErrorEvent/RetrieveErrorRecord

読み取りでエラーが発生した場合、その読み取りは完了としてマークされます。 以降のキュー内の通知はブロックされません。 エラーの通知は、キューに関連付けられた Event オブジェクトを通じて処理されます。これは GetErrorEvent を使用して取得できます。 タイトルは GetErrorEvent によって返される Event に対し、WaitForSingleObject を使用できます。 イベントが通知された場合、タイトルは RetrieveErrorRecord 関数を呼び出して、前回の RetrieveErrorRecord 関数の呼び出し以降最初のエラーを取得できます。

RetrieveErrorRecord によって返されるエラー レコードには、前回の RetrieveErrorRecord 以降にキューで最初に失敗した要求のデータのみが含まれます。 エラー イベントがシグナル状態でない場合、またはデータが既に取得されている場合、エラー レコードのデータは未定義です。

クエリ

キューに関する情報を取得します。 キューの作成に使用される DSTORAGE_QUEUE_DESC 構造、および自動送信をトリガーするためにキューに入れらえる必要がある空のスロットの数とエントリの数が含まれます。

ベスト プラクティス

Win32 向けのベスト プラクティスに関するアドバイスは、DirectStorage にも当てはまります。 最適なパフォーマンスのしきい値は大幅に変更されています。

読み取りサイズ

回転ディスクに関する当初のアドバイスは、少なくとも 128 KiB のブロックを読み取るというものでした。 パフォーマンスはブロック サイズが大きくなるにつれて向上します。 最高のパフォーマンスを達成したのは 512 KiB のサイズのブロックでした。

NVMe で可変要素がない場合は、しきい値が大幅に低下します。 読み取りのパフォーマンスは、32 KiB の読み取りから大きく変化し始め、64 KiB 以降は頭打ちとなります。 このサイズを超える読み取りでは、パフォーマンスは向上しません。 これは、最適なパフォーマンスを得るためにデータをマージして大きなブロックによるパッケージを生成するために必要な労力が少なくなることを意味します。

サイズが 512 KiB を超える場合に展開を使用すると、より多くの小サイズの要求の並行処理が、大サイズの 1 つの要求の処理より優先されます。 1 つの大きな要求では、展開が強制的にシリアル化されますが、複数の同時発生要求では、複数の展開ハードウェア ユニットを並列で動作させ、完全なスループットを実現できます。

Microsoft Game Development Kit (GDK) の 2022 年 10 月リリースでは、ソースと宛先のメモリ使用量を組み合わせた場合に、1 つの要求の最大サイズが宛先として 32MiB から 1GiB に増やされました。 これは、大きな読み取りサイズを許可する他のストレージ API からの移植を容易にするために用意されています。 ただし、大規模な要求では複数の展開ハードウェア ユニットが並列で動作することはできないため、最大スループットを実現するための上記のサイズの推奨事項は引き続き変わりません。

並べ替え

従来、回転ディスクではディスク上の読み取りの場所を並べ替える作業に手間がかかっていました。 理想的なのは、ディスク上の連続した場所から読み取りを行うことでした。 これにより、シーク時間が係数として削除された、ディスク ヘッドによる移動の最小量が作成されました。 これを行うと、パフォーマンスが桁違いに向上する可能性があります。 また場所別にランダムな読み取りを送信することにも利点があり、場合によっては速度が 2 倍になっていました。

NVMe ドライブで読み取り要求をできるだけ連続するように並べ替えることは、現在も有用です。 NVMe ドライブでは、64 KiB にアライメントされたブロックが読み取られます。 このため、64 KiB ブロックの未使用セクションを読み込むための帯域幅は無駄になる可能性があります。 読み取り要求が 4 KiB のみに対するものである場合は、60 KiB の帯域幅が無駄になります。 NVMe は可能であれば、他の保留中の要求を処理するためにこの余分な 60 KiB を再利用します。 たとえば、2 つの連続した読み取りがあり、1 つが 32 KiB でその後に 8 KiB がある場合、ドライブからは 64 KiB の読み取りが 1 つだけ実行されます。

キュー管理

ローテーション ディスクの推奨事項は、キュー サイズを 12 ~ 16 にすることでした。キューの深さを大きくしても利点はなく、キューの深さを小さくするとパフォーマンスが大幅に低下します。

NVMe の仕様では、NVMe ドライブが複数のキューをサポートし、それぞれのキューは最大 65,536 エントリの深さを持つ必要があることが示されています。 DirectStorage ではこの要件がサポートされているため、タイトルは一度に数千の要求を送信できます。

従来の回転ディスクでは、タイトルが保留中の要求をバッファーに入れて、キューの深さを 12 ~ 16 の範囲に収めていました。 DirectStorage に関する推奨事項は、要求をバッファーに入れず、作成直後にキューに入れることです。 システム全体がパイプラインであるため、タイトルをバッファーに入れるとパイプライン内にバブルが生じ、パフォーマンスが大幅に低下する可能性があります。

もう 1 つの推奨事項は、フレームごとに作成される要求の数のうち少なくとも 4 倍の容量を持つキューを作成することです。 これにより、既存の要求が完了するのを待つ間ストールすることなく新しい要求を追加できるよう、十分な容量が確保されることになります。

通知の管理

一般に、キューに追加する通知要求の数が少ないほど、パフォーマンスは向上します。 推奨されるのは、タイトルのニーズと、キューに登録する通知要求を最小限に抑えることとのバランスを取ることです。 各要求の後で通知をキューに入れると、それらの通知の処理オーバーヘッドが増えるため、全体的なパフォーマンスが低下するだけです。

1 つの例として、コンテンツのグループ化が挙げられます。 たとえば、SFS テクスチャ、地形のニーズ (メッシュやテクスチャなど)、アクターのニーズ (メッシュ、テクスチャ、アニメーションなど) などです。 これにより、オブジェクトの作成に必要なすべてのアセットの可用性に単一の通知をバインドできます。

ID3D12Fence とステータス配列のどちらを使用すべきかは、タイトルのニーズによって決まります。 GPU がデータをすぐに使用する必要があるか。 読み取りが完了するまでチェック スレッドが一時停止することは許容できるか。 データをフレーム内の特定のポイントでのみ処理すればよいため、定期的なポーリングで十分であるか。

考慮事項

はるかに多くの要求が処理中である可能性を踏まえて、タイトルの別の部分でボトルネックが発生することを避けるため、少し注意を払う必要があります。 タイトルが要求を管理するためのコストは、DirectStorage による節約分をすぐに超過してしまう可能性があります。 要求ごとにすべてのサポート コードを確認し、最小化できる事項を判断することをお勧めします。

それぞれの要求が新しいメモリ ブロックの割り当てを必要とするか。

  • メモリ システムには、新しいブロックを見つけて内部リストを更新するというオーバーヘッドがあります。
  • メモリ ブロックはできるだけ再利用することを検討してください。

マネージャーの更新にはロックが必要か。

  • 多くの更新を実行するほど、多くの競合が発生します。
  • できるだけロックを使用しない方法を検討してください。

投機的読み込みは使用されるか。

  • キャンセルがサポートされているため、より多くの要求が作成される可能性があります。
  • ただし、投機にはメモリを使用できる必要があります。
  • 投機しきい値に関してはハード リミットを考慮してください。

関連項目

DirectStorage

DirectStorage の使用方法と内部の詳細 (NDA トピック)認可が必須です

DirectStorage と XBTC (NDA トピック) を使用した圧縮コンテンツの最適化認可が必須です

DirectStorage のパフォーマンスの分析 (NDA トピック)認可が必須です