Skip to main content

はじめに

この記事では、XBOX Series X|S コンソール専用の DirectStorage API の概要を説明します。デスクトップの DirectStorage の詳細については DirectStorage on Desktop を参照してください。 PCIe バスで接続された最新の NVMe ストレージ デバイスは、非常に高いスループットと IOPS (1 秒あたりの I/O リクエスト数) を実現できます。Win32 API のオーバーヘッドにより、利用可能なストレージ帯域幅は活用できるものの、それを活かそうとすると許容できないほど高い CPU 使用率になる場合があります。特にワークロードが多数の小さなリクエストで構成される場合に顕著です。 DirectStorage API は、基盤となる NVMe ハードウェアと緊密に連携することで、オペレーティング システムのオーバーヘッドの大半を除去するように設計されています。これにより、より少ない CPU 使用率でより高い帯域幅を実現します。目標は、CPU コア 1 個の最大 10% を使用して 1 秒あたり最大 50,000 リクエストを処理できるようにすることです。

既存の問題

コンソール世代が進むにつれて高解像度アセットの需要が高まり、ゲーム コンテンツはますます大きくなっています。既存の XBOX One ハードウェアとソフトウェアには、次世代コンテンツでハード ドライブからメモリにデータを取得する開発者の能力を妨げるいくつかの制限があります。
  • CPU 使用率が高い
    • 既存の Win32 API はオーバーヘッドで CPU コアを 1 つ丸ごと使用する可能性があります。
    • これはタイトルからのリクエスト数に依存します。
  • ディスクからの最大帯域幅が不十分
  • ディスク リクエストの優先度付けができない
    • タイトル リクエストの優先度付けができないと、応答性のあるストリーミング システムの構築が難しい場合があります。
  • ディスク リクエストのキャンセルができない
    • リクエストのキャンセルができないと、投機的読み取りシステムの構築が難しい場合があります。
  • ハードウェア アクセラレーテッド展開がない
    • ハードウェア アクセラレーテッド展開がないと、ソフトウェアで展開を行うのに多くの CPU リソースがかかる場合があります。
DirectStorage API セットは、これらの問題それぞれに直接対処します。全体的な効果として、XBOX ファイル システムのパフォーマンスが大幅に向上します。

CPU 使用率

DirectStorage の主要設計目標は、タイトルが 50K IOPS を維持しつつ、CPU コア 1 個の 5〜10% しか使用しないようにすることです。これにより、タイトルは NVMe ストレージ サブシステムから最大の帯域幅を達成しつつ、同時に CPU を他のタイトル要件に使用できます。 DirectStorage はハードウェア展開のサポートも追加します。各読み取りリクエストは、NVMe ドライブから内蔵ハードウェア展開ブロックに直接ルーティングできます。これにより、タイトルが展開に CPU リソースを費やす必要がなくなります。

キューイング パイプライン モデル

DirectStorage はバッチ方式を使用し、複数のリクエストをキューに追加します。後の時点でキューが次のパイプライン ステージにフラッシュされます。これにより、パイプライン ステージ間の遷移にかかる全体的な CPU コストがすぐに削減されます。既存の Win32 API セットでは、リクエストごとに遷移があります。DirectStorage のキューは競合を最小化するためにロックフリー アルゴリズムを利用します。各キューがいつフラッシュされるかはタイトルが制御できます。 Win32 API を使用する多くの場合、ディスクからのデータを別のバッファーにコピーする必要がある場合があります。場合によっては、データを複数回コピーする必要があるかもしれません。DirectStorage は、タイトルが提供する宛先バッファーを各パイプライン レイヤーに直接マッピングすることで、この問題を解消します。ハードウェアはタイトルが提供したバッファーに直接書き込みます。 これらの変更が CPU オーバーヘッドの大幅な削減に寄与しています。

展開

ハードウェアによるデータ展開の能力が向上しました。より多様な形式を、NVMe サブシステムがデータを提供できる速度を超える速度で処理できるようになりました。加えて、DirectStorage はインプレース展開をサポートし、圧縮データと展開データ用に別々のバッファーを管理する必要をなくします。 ハードウェアは BCPACKDEFLATE をサポートし、最終的なコンテンツを swizzle する機能を提供します。これらの形式は相互排他的ではありません。3 つすべてをデータに適用することも可能です。これにより、タイトルは最良の圧縮率とパフォーマンスをもたらす方法を選択できます。アセットごとに異なる圧縮と swizzle 設定を使用できます。

キューの深さ

以前の推奨は、回転ディスクでは同時に 12〜16 個の非同期リクエストのみを進行中に保つことでした。それ以上大きくしてもパフォーマンス上のメリットはなく、小さくすると大幅にパフォーマンスが低下しました。これにより、タイトルは推奨ターゲット内に留まるよう、未完了の読み取りリクエストのバランスを取る追加作業を実行していました。 タイトルが 50,000 IOPS を達成できるようにするという DirectStorage の目標により、推奨が変わりました。タイトルは未完了作業とキューの深さのバランスを取ろうとする必要はなくなりました。タイトルはすべての未完了リクエストを送信すべきです。リクエストを保留するメリットはありません。多くの場合、リクエストを保留するとハードウェアが新しいリクエストを待って停止するため、パフォーマンスが低下する可能性があります。 オペレーティング システムは、場合によっては (ディスクの断片化への対応など) より大きな読み取りリクエストをいくつかの小さなリクエストに分割する必要があります。ただし、これは DirectStorage のアーキテクチャで考慮されています。50,000 IOPS の設計目標は、ハードウェアに送信される最終リクエストではなく、タイトルの IO 操作数に基づいています。

通知

Win32 アーキテクチャでは、読み取り完了通知に大きなオーバーヘッドが費やされます。タイトルは OVERLAPPED 構造体をポーリングするか、関連する Event ハンドルを待つか、同期ブロッキング読み取りを実行できます。全体として、これは各読み取りリクエストのリソース要求を増加させます。 DirectStorage は 2 つの非同期通知の概念を維持しつつ、3 つ目の方法も追加します。DirectStorage は同期ブロッキング読み取りをサポートしません。タイトルが独自のシステムを実装することは可能ですが、推奨されません。 1 つ目の非同期方法は、関連するリクエストが完了した際に設定されるステータス ブロックを通じて実装されます。タイトルは必要に応じてブロックをポーリングして読み取りが完了したかを判定できます。これは、OVERLAPPED 構造体を完了についてポーリングする Win32 の方法に似ています。 2 つ目の非同期方法は、Windows の Event オブジェクトを使用して完了を通知します。これは、対応する Event オブジェクトを持つ OVERLAPPED 構造体の使用に似ています。タイトルは WaitForSingleObject メソッドを使って、読み取り操作が完了するまで呼び出し元スレッドを一時停止できます。 3 つ目の非同期方法は、ID3D12Fence を使用して実装されます。タイトルはフェンスを待機して一時停止することも、必要に応じてフェンスをポーリングすることもできます。GPU もフェンスを使用して完了したリクエストを直接通知できるという利点もあります。 DirectStorage の通知システムは単一の読み取りリクエストにバインドされません。キュー内に配置されるエントリで、それより前のすべての読み取りリクエストが完了した際にシグナルが立ちます。これによりタイトルは通知の粒度を制御できます。通知は常にキューの順序でシグナルが立ちます。キューは FIFO (先入れ先出し) キューと考えることができます。タイトルは最後に関連する通知を照会するだけで済みます。それ以前にエンキューされたリクエストはすべて完了したことが保証されます。

メモリ間展開

DirectStorage は、ディスク ファイルではなくメモリを展開ソースとして展開ハードウェアを呼び出すキュー タイプを提供します。これにより、圧縮アセットがファイルから取得されない場合や、以前に取得してキャッシュとしてメモリに保持されている場合でも展開ハードウェアを活用できます。メモリ ソースのキューはメモリ ソースのリクエストのみを受け入れ、ファイル ソースのキューはファイル ソースのリクエストのみを受け入れます。 メモリ ソースのリクエストで展開オプションが指定されていない場合、展開ハードウェアは DMA コピー エンジンとしても動作できます。 DirectStorage は完了通知の順序を保証しますが、リクエストの処理が開始されるタイミングは保証しません。したがって、保留中のリクエスト間にデータの依存関係があってはなりません。すなわち、リクエスト A の宛先を、リクエスト A の完了後にリクエスト B がエンキューされる場合を除き、リクエスト B のソースとして使用できません。 メモリ ソースのキューは real-time 優先度で作成する必要があります。さらに、メモリ ソースの real-time リクエストは、展開を必要とするディスク ソースのリクエストよりも常に先に展開ハードウェアで処理されます。ディスク ソースのキューに展開リクエストがない場合、2 つのキュー タイプは互いに影響することなく完全に並列に処理されます。

優先度

DirectStorage では各キューに優先度レベルを割り当てられます。キュー内の各エントリはキューの優先度を継承します。real-time、high、normal、low の 4 つの優先度レベルが提供されます。リクエストは重み付きラウンド ロビン方式で処理されます。たとえば、normal 優先度のリクエストを 1 つ処理する前に high 優先度の X リクエストを処理する、low 優先度のリクエストを 1 つ処理する前に normal 優先度の Y リクエストを処理する、といった具合です。 優先度の重みは各リクエストのサイズに対してカウントされます。既定の各優先度間の重みはおよそ 10 倍です。つまり、low 優先度のリクエスト 1 KB あたり、medium 優先度のリクエスト 10 KB、high 優先度のリクエスト 100 KB が処理されることになります。 既存の Win32 読み取りリクエストは同じ優先度システムを経由してルーティングされます。すべての Win32 リクエストは normal 優先度とみなされます。 メモリ ソースのキューは real-time 優先度で作成する必要があります。

キャンセル

各 DirectStorage 読み取りリクエストには、タイトルが提供する 64 ビット マスクが関連付けられます。これは保留中の読み取りリクエストのキャンセルをサポートするためです。タイトルはマスク内の特定のフラグ セットと一致するリクエストをキャンセルできます。 キャンセルをサポートしていても、読み取りリクエストがハードウェアによって処理される場合があります。タイトルのキャンセル要求はベスト エフォートの試みです。リクエストがハードウェアで既にアクティブに処理されている場合、キャンセルできません。 キャンセル要求はベスト エフォートであるため、タイトルは読み取りリクエストの処理が完了したという通知を受け取るまで待つ必要があります。タイトルはキュー内の後の通知を受け取るまで必要なリソースを解放できません。ただし、この時間中も、以前のキャンセル要求で使用されたフラグに一致する新しいリクエストをエンキューでき、それらはキャンセルされません。 キャンセルされたリクエストが完了した場合、キャンセルされ完全な結果を生成しなかった場合でも、成功 とみなされます。言い換えると、リクエストに対してキャンセルが試みられた場合、タイトルは完了時にキャンセルされる可能性のあるリクエストの結果を使用できなくなります。

保証

XBOX One および XBOX One S コンソールの最低保証は 40 MB/s でした。XBOX One X コンソールは最低保証を 60 MB/s に引き上げました。これらの数値は、実際のハードウェア上限 (130 MB/s 域) を大きく下回ります。これは完全にオペレーティング システムによるオーバーヘッドが原因でした。 DirectStorage はオペレーティング システムによるオーバーヘッドの大半を除去します。これにより、ハードウェア上限に近い最低保証が可能になります。新しい最低パフォーマンス保証は、生データに対して 250 ms ウィンドウで 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
    • ストレージ デバイスから読み込まれたコンテンツを復号/展開する前に一時保存する staging buffer のサイズを設定します。メモリ ソースのキューのみを使用する場合、staging buffer は 0 サイズにできます。

IDStorageFactoryX1

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

IDStorageFactoryX2

IDStorageFactoryX2 インターフェイスは、IDStorageFactoryX1 インターフェイスを CreateQueue1 メソッドで拡張します。
  • CreateQueue1
    • IDStorageQueueX2 オブジェクトを作成します。新しい構造体を使用してキューの自動送信機能をオーバーライドする機能など、キュー作成時に追加のオプションを許可します。

IDStorageFileX

すべてのファイルは、最初に IDStorageFactoryX オブジェクトを通じて DirectStorage でオープンする必要があります。これは Win32 API の CreateFile の使用に相当します。 ファイルは FILE_SHARED_READ アクセス許可でオープンされます。必要であれば、タイトルは適切なアクセス許可を尊重したうえで Win32 API を使用して同時にファイルをオープンできます。開発中は loose とパッケージのデプロイの両方がサポートされます。 ファイルは、ファイル オブジェクトの Close 関数を明示的に呼び出すか、対応する IDStorageFileX オブジェクトへの最後の参照が解放された時にクローズされます。ただし、ファイルをクローズできる前にすべての未完了 I/O 操作が完了している必要があります。つまり、ファイルをクローズする両方の方法とも、そのファイルの未完了 I/O 操作がすべて完了するまでブロックします。 ゲームは、GetHandle 関数を呼び出すことで、IDStorageFileX オブジェクトが表すファイルへの win32 ハンドルを取得できます。ハンドルは GENERIC_READ アクセス許可と FILE_SHARE_READ 共有モードでオープンされます。ファイル サイズの照会などに使用できます。ハンドルは不要になったら CloseHandle() でクローズする必要があります。

IDStorageQueueX

読み取りリクエストは IDStorageQueueX オブジェクトを通じて NVMe に送信されます。ただし、タイトルがキューで Submit を呼び出すか、Enqueue メソッドの 1 つが前回の送信以来キュー容量の半分以上を埋めて自動送信をトリガーするまで、リクエストはデバイスに送信されません。送信はパイプラインの次のステージへの単一遷移として処理されます。これによりタイトルはタイトルとカーネル間の遷移で CPU コストが発生するタイミングを制御できます。 IDStorageQueueX オブジェクトには 4 つのプロパティがあります。
  • SourceType
    • キューがファイル ソースのリクエストを受け入れるか、メモリ ソースのリクエストを受け入れるかを指定します。
  • Priority
    • キューに送信されるすべてのリクエストの優先度: real-time、high、normal、low。
    • メモリ ソースのキューは real-time 優先度で作成する必要があります。
    • リクエストは優先度に基づいて重み付きラウンド ロビン順に処理されます。
    • Win32 リクエストは normal 優先度レベルで処理されます。
  • Capacity
    • キューが保持できる未完了リクエストの最大数。
    • キューが容量に達している時にリクエストをエンキューしようとすると、エントリがハードウェアによって完了されるまでブロックされます。
    • キューに必要なメモリの量は、およそキュー容量に DSTORAGE_REQUEST のサイズを掛けたものです。
  • Name
    • これは純粋にデバッグを助けるためのものです。名前は DirectStorage コードでは使用されませんが、PIX (NDA トピック) などの開発者ツールで表示されます。
ハードウェアはスループットを最大化するためリクエストを非同期的に処理します。ただし、Win32 とは異なり、タイトルは FIFO 順で完了通知を受け取ります。完了通知を受け取ると、同じキューへのそれ以前のすべてのリクエストも完了していることが保証されます。

IDStorageQueueX1

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

IDStorageQueueX2

IDStorageQueueX2 インターフェイスは、IDStorageQueueX1 インターフェイスを CreateQueue1 メソッドで拡張します。 加えて、以下の 1 つの追加プロパティがあります。
  • Options
    • 自動送信の無効化を含む、キューの動作を制御するフラグ。

EnqueueRequest

このインターフェイスは機能的に Win32 の ReadFile インターフェイスと同じです。個別の読み取りリクエストが作成されキューに送信されます。主な違いは、DirectStorage が送信前に多くのリクエストをキューに入れられること、ハードウェア展開をサポートすること、キャンセルをサポートすることです。 リクエストにはいくつかの主要プロパティがあります。 リクエストのソース Options.SourceTypeOptions.SourceIsPhysicalPages の組み合わせに応じて、DirectStorage はソース データの場所を指定するために以下の 3 つのプロパティ グループのいずれかを使用します。
  • File および FileOffset
    • このグループは Options.SourceTypeDSTORAGE_REQUEST_SOURCE_FILE の場合に使用されます。
    • File は事前に IDStorageFactoryX::OpenFile でオープンしておきます。
    • FileOffset は、展開を使用する場合は 16 バイト アラインメントが必要で、展開を使用しない場合はアラインメント要件はありません。
      • これは、非同期読み取りに対してファイル内で 4 KiB アラインメントを必要とした Win32 からの大きな変更点です。
  • Source
    • このグループは Options.SourceTypeDSTORAGE_REQUEST_SOURCE_MEMORY かつ Options.SourceIsPhysicalPagesFALSE の場合に使用されます。
    • 展開されるデータを保持するメモリ バッファーです。
  • SourcePageArray および SourcePageOffset
    • このグループは Options.SourceTypeDSTORAGE_REQUEST_SOURCE_MEMORY かつ Options.SourceIsPhysicalPagesTRUE の場合に使用されます。
    • Source に似ていますが、ソース メモリ バッファーを 64 KB 物理ページの配列と最初のページ内のバイト オフセットの形で提供します。
    • 物理 64 KB ページは XMemAllocatePhysicalPages で割り当てられます。
SourceSize
  • メモリ バッファーまたはファイルから読み取るソース データのサイズ (バイト単位)。
IntermediateSize
  • このリクエストで zlibBCPACK 展開の両方が有効な場合、IntermediateSize は、ソース データが zlib 展開後に (BCPACK 展開前に) なる中間サイズを指定するために使用されます。
  • それ以外の場合は 0 に設定します。
リクエストの宛先 Options.DestinationIsPhysicalPages に応じて、DirectStorage は宛先の場所を指定するために以下の 2 つのプロパティ グループのいずれかを使用します。
  • Destination
    • このグループは Options.DestinationIsPhysicalPagesFALSE の場合に使用されます。
    • 最終的にロードされたデータの宛先バッファーです。
    • 展開は共有の内部バッファーを使用して行われ、in-place と見なせます。
  • DestinationPageArray および DestinationPageOffset
    • このグループは Options.DestinationIsPhysicalPagesTRUE の場合に使用されます。
    • Destination に似ていますが、宛先メモリ バッファーを 64 KB 物理ページの配列と最初のページ内のバイト オフセットの形で提供します。
    • 物理 64 KB ページは XMemAllocatePhysicalPages で割り当てられます。
DestinationSize
  • 最終的にロードされるコンテンツの予想サイズ (バイト単位)。宛先には操作に対応する十分な空間が必要です。
  • サイズは展開を使用しない場合は SourceSize と等しく、展開を使用する場合は SourceSize より大きくなければなりません。
CancellationTag
  • タイトルが定義する任意の 64 ビット タグ。
  • このタグはキャンセル要求のマスクとして使用されます。
Name
  • デバッグを助けるためのオプションの文字列。Name は PIX (NDA トピック) などの開発者ツール、または IDStorageQueueX::RetrieveErrorRecord から取得されるエラー レコードに現れる場合があります。Name 文字列はリクエストの存続期間中アクセス可能である必要があります。
Options
  • ZlibDecompress
    • データを RFC 1950 展開標準で展開する必要があることを示します。
  • BcpackMode
    • データの展開に使用する BCPACK のモードを示します。
    • None は有効なオプションで、データが BCPACK 圧縮されていないことを意味します。
  • SwizzleMode
    • 最終データをメモリ内でどのように swizzle するかを示します。
  • DestinationIsPhysicalPages
    • 宛先バッファーが Destination ではなく DestinationPageArrayDestinationPageOffset で指定されることを示します。
  • SourceType
    • リクエストはメモリ ソース (Source/SourcePageArraySourcePageOffset プロパティを持つ) またはファイル ソース (File/FileOffset プロパティを持つ) のどちらかです。
  • SourceIsPhysicalPages
    • ソース バッファーが Source ではなく SourcePageArraySourcePageOffset で指定されることを示します。

EnqueueStatus/EnqueueSignal/EnqueueSetEvent

リクエストをエンキューして、関連するリクエストの一連として扱うことができます。これは、キュー内の特定の位置に処理が到達した際の通知をエンキューすることで実現されます。通知は、それ以前のすべての読み取りリクエストが完了した場合にのみ処理されます。これにより、それ以前のリクエストからのデータが直ちに利用可能であることが保証されます。 タイトルには、通知のための 2 つのポーリング方法と 1 つの待機方法があります。タイトルは ID3D12Fence オブジェクト、IDStorageStatusArrayX オブジェクト、または set event 操作を挿入できます。ID3D12FenceID3D12Fence オブジェクトに期待されるとおりに動作します。タイトル スレッドは Event を待機でき、CPU はフェンスをポーリングでき、GPU もフェンスをポーリングできます。IDStorageStatusArrayX オブジェクトは CPU が完了をポーリングし、発生し得る読み取り失敗にアクセスできます。EnqueueSetEvent メソッドを使用すると、タイトル スレッドはポーリングではなく指定されたイベントを待機できます。これは ID3D12Fence::SetEventOnCompletion とは異なります。XBOX 実装の ID3D12Fence::SetEventOnCompletion はシグナルが立つまでフェンスをスピンし、CPU ハードウェア スレッドをシグナルまで消費します。一方 EnqueueSetEvent を使うと、タイトル スレッドはイベントがシグナルされるまで WaitForSingleObject/WaitForMultipleObjects を使って他のスレッドに CPU を譲れます。 前述のとおり、基盤となるハードウェアがパフォーマンスのために順序変更しても、すべてのリクエストは順に完了します。キュー上の以前にエンキューされたリクエストがすべて完了するまで、通知はシグナルされません。

展開

展開は専用ハードウェアで処理されます。これにより、従来の展開アルゴリズムによる CPU オーバーヘッドを除去します。DirectStorage は初期化時に展開用の作業バッファーとして使用する固定ブロックのメモリを割り当てます。これによりインプレース展開が可能になり、圧縮データと展開データを同時にメモリに保持する必要がなくなります。 展開ハードウェアは 3 つの動作モードをサポートします。モードは相互排他的ではないため、モードの任意の組み合わせを指定できます。展開モードは次の順序で適用されます: DEFLATEBCPACKSwizzle
  • ZLibDecompress
  • BCPack
    • BCPack は BCn データ専用に設計されたカスタム エントロピー コーダーです。一般に、これはカラー エンドポイントがパレット インデックス (すなわち重み) から分離され、rANS アルゴリズムを使って圧縮されることを意味します。
  • Swizzle
    • Swizzle および shuffle モードは、コンテンツ パイプラインに追加の最適化を提供できます。
高エントロピーのデータを圧縮する場合、圧縮によって実際にサイズが増加することがあります。逆に、対応する展開ではサイズが縮小します。DirectStorage は縮小型展開を許可しないため、圧縮不可能な高エントロピー データを検出してそのようなアセットで圧縮を避けるのはタイトルの責任です。詳細は DirectStorage と XBTC を使用した圧縮コンテンツの最適化 (NDA トピック) を参照してください。

Staging buffer

DirectStorage は、復号や展開などの操作を実行する前に、生の NVMe ストレージから読み込まれたすべてのコンテンツをステージングするためのバッファーを内部で使用します。この staging buffer により、NVMe ドライブと復号/展開シリコンをパイプラインで並列動作させることができます。既定値は 32 MiB で、最初の DirectStorage ファクトリ ポインターを取得した時に割り当てられます。 タイトルが DirectStorage をメモリ間展開操作にのみ使用する場合、staging buffer は不要で、SetStagingBufferSize を呼び出して staging buffer サイズを 0 に設定できます。 現在のリリースの DirectStorage は、0、16、20、24、28、32 MiB の staging buffer サイズをサポートします。既定より小さな値のサイズはタイトルのメモリを節約しますが、全体的な読み取りパフォーマンスに影響する場合があります。 注意: SetStagingBufferSizeIDStorageQueueX オブジェクトまたは IDStorageFileX オブジェクトが存在しない場合にのみ呼び出せます。それ以外の場合はエラーがスローされます。

CancelRequestsWithTag

DirectStorage はリクエストのキャンセルをサポートします。各リクエストにはタイトル定義の 64 ビット タグが関連付けられます。これは、どのリクエストをキャンセルするかのビット マスクとして機能します。タイトルはキャンセル用のマスクと値を提供します。キューは tag & mask == value の条件に一致するすべてのリクエストのキャンセルを試みます。 キャンセルはベスト エフォート操作です。パイプライン内のリクエストの位置によっては、キャンセルできない場合があります。たとえば、リクエストがハードウェアによって展開中の場合、これはキャンセルできません。API は直ちに返り、キャンセルされたすべてのリクエストが処理されるのを待つことはブロックしません。タイトルは、キャンセルされたリクエストに関連するリソースを解放する前に、キュー内の後の通知がシグナルされるまで待つ必要があります。 CancelRequestsWithTag が呼び出されているのと同時にキューにリクエストを追加することを避けるよう注意してください。この場合の動作は未定義です。ただし、CancelRequestsWithTag の呼び出しが戻った後にキューに追加されたリクエストは、条件に一致してもキャンセルされません。以前にエンキューされたリクエストのみがキャンセルされます。

GetErrorEvent/RetrieveErrorRecord

読み取りでエラーが発生した場合、その読み取りは完了としてマークされます。キュー内の将来の通知はシグナルからブロックされません。エラーの通知は、キューに関連付けられた Event オブジェクトを通じて処理されます。これは GetErrorEvent で取得できます。タイトルは GetErrorEvent から返された Event に対して WaitForSingleObject を使用できます。イベントがシグナルされた場合、タイトルは RetrieveErrorRecord 関数を呼び出して、前回の RetrieveErrorRecord 関数の呼び出し以降の最初のエラーを取得できます。 RetrieveErrorRecord から返されるエラー レコードには、前回の RetrieveErrorRecord 以降のキュー内で最初に失敗したリクエストのデータのみが含まれます。エラー Event がシグナルされていない場合、またはデータが既に取得されている場合、エラー レコードのデータは未定義です。

Query

キューに関する情報を取得します。キューの作成に使用される DSTORAGE_QUEUE_DESC または DSTORAGE_QUEUE_DESC1 構造体、空きスロット数、自動送信をトリガーするためにエンキューする必要があるエントリ数などが含まれます。

ベスト プラクティス

Win32 で提供されたベスト プラクティスのアドバイスは、DirectStorage にも適用されます。最適なパフォーマンスのしきい値は大きく変わりました。

読み取りサイズ

回転ディスク向けの元のアドバイスは、少なくとも 128 KiB ブロックで読み取ることでした。ブロック サイズを大きくするとパフォーマンスは向上し続けました。512 KiB サイズのブロックが最高のパフォーマンスを達成しました。 NVMe には可動部品がないため、しきい値ははるかに低くなります。読み取りパフォーマンスは 32 KiB の読み取りから大きく跳ね上がり、64 KiB でピークに達します。それより大きくしてもパフォーマンスは向上しません。つまり、最適なパフォーマンスを達成するためにパッケージ内でデータを大きなブロックにマージする作業を減らせることになります。 512 KiB を超える場合、展開を使用するなら、単一の巨大なリクエストではなく、より小さく多くのリクエストを並列で使用することを優先してください。単一の巨大なリクエストは展開の直列化を強制しますが、複数の同時実行リクエストは複数の展開ハードウェア ユニットが並列に動作して完全なスループットを達成できるようにします。 2022 年 10 月のリリースの Microsoft Game Development Kit (GDK) では、単一リクエストの最大サイズが宛先として 32 MiB から、結合 (ソース + 宛先) メモリ使用量で 1 GiB に増加しました。これは、大きな読み取りサイズを許可する他のストレージ API からの移植を容易にするために提供されます。ただし、最大スループット達成のための以前のサイズ推奨は同じままです。大きなリクエストは複数の展開ハードウェア ユニットが並列に動作することを許さないためです。

順序

以前の回転ディスクでは、ディスク上の読み取り位置を順序付けることに労力が費やされました。理想的な状況は、ディスク上の連続した位置から読み取ることでした。これによりディスク ヘッドの移動が最小化され、シーク時間が要因から除去されました。これを行うことでパフォーマンスが桁違いに向上する可能性がありました。ランダムな読み取りを位置でソートして送信することにもメリットがあり、場合によっては 2 倍高速でした。 NVMe ドライブ上でも、読み取りリクエストをできるだけ順次にすることには依然として有用性があります。NVMe ドライブは 64 KiB アラインメントのブロックで読み取ります。このため、64 KiB ブロックの未使用部分を読み取ることで帯域幅が無駄になる可能性があります。読み取り要求が 4 KiB のみの場合、60 KiB の帯域幅が無駄になります。可能であれば、NVMe は他の保留中のリクエストを満たすためにその余分な 60 KiB を再利用します。たとえば、2 つの連続する読み取りがあり、1 つが 32 KiB でもう 1 つが 8 KiB の場合、ドライブからは依然として 1 回の 64 KiB 読み取りしかありません。

キュー管理

回転ディスクの推奨は、12 から 16 の間のキュー サイズを持つことでした。それより大きなキュー深さにメリットはなく、それより小さなキュー深さを使うとパフォーマンスの低下は大きくなりました。 NVMe 仕様では、NVMe ドライブはキューあたり最大 65,536 エントリの深さの複数のキューをサポートすべきとされています。DirectStorage はこの要件をサポートし、タイトルが一度に数千のリクエストを送信できるようにしています。 以前の回転ディスクでは、タイトルは 12〜16 の範囲のキュー深さを維持するために保留中のリクエストをバッファリングしていました。DirectStorage の推奨は、リクエストをバッファリングせず、作成された時点でリクエストをエンキューすることです。システム全体はパイプラインであり、タイトルのバッファリングはパイプラインにバブルを作り出し、パフォーマンスを大幅に低下させる可能性があります。 もう 1 つの推奨は、フレームあたりに作成されるリクエスト数の最大値の少なくとも 4 倍のキャパシティを持つキューを作成することです。これにより、既存のリクエストの完了を待つ停止なしに、新しいリクエストを追加できる十分なキャパシティが確保されるべきです。

通知の管理

一般に、キューに追加する通知リクエストは少ないほど良いです。推奨は、タイトルのニーズとエンキューされる通知リクエストを最小限に抑えることの間でバランスを見つけることです。各リクエストの後に通知をエンキューすると、それらの通知の処理オーバーヘッド増加により、全体的なパフォーマンスを低下させるだけです。 例として、コンテンツごとにグループ化することが挙げられます。たとえば、SFS テクスチャ、地形のニーズ (メッシュとテクスチャなど)、アクターのニーズ (メッシュ、テクスチャ、アニメーションなど) です。これにより、オブジェクトを作成するのに必要なすべてのアセットの利用可能性に対して単一の通知をバインドできます。 ID3D12Fence を使用するかステータス配列を使用するかの選択は、タイトルのニーズによります。データを GPU で直ちに使う必要があるか。チェック スレッドが読み取りが完了するまで一時停止することは許容できるか。データがフレーム内の特定のポイントでのみ処理できるため、定期的なポーリングで十分か。

考慮事項

飛行中のリクエスト数が非常に多くなる可能性があるため、タイトルの他の部分にボトルネックを作らないように注意する必要があります。タイトルがリクエストを管理するためのコストが、DirectStorage で節約された分をすぐに上回る可能性があります。推奨は、リクエストごとのすべてのサポート コードを確認し、何を最小化できるかを判断することです。 各リクエストは新しいメモリ ブロックの割り当てを必要とするか?
  • メモリ システムは新しいブロックを見つけて内部リストを更新するオーバーヘッドを持ちます。
  • 可能な限りメモリ ブロックを再利用することを検討してください。
マネージャーを更新するためにロックが必要か?
  • 更新の実行数が増えるとより多くの競合を生みます。
  • 可能な限りロックフリーにすることを検討してください。
投機的ロードが使用されているか?
  • キャンセルはサポートされていますが、より多くのリクエストの作成につながる可能性があります。
  • ただし、投機のためにメモリが利用可能である必要があります。
  • 投機のしきい値に厳しい制限を設けることを検討してください。

関連項目

DirectStorage DirectStorage の使用方法と内部詳細 (NDA トピック) DirectStorage と XBTC を使用した圧縮コンテンツの最適化 (NDA トピック) DirectStorage パフォーマンスの解析 (NDA トピック)
最終更新日 2026年8月24日