Introduction
Cet article fournit une vue d’ensemble des API DirectStorage pour les consoles XBOX Series X|S uniquement. Consultez DirectStorage sur ordinateur de bureau pour plus d’informations sur DirectStorage sur ordinateur de bureau. Les derniers périphériques de stockage NVMe connectés via un bus PCIe peuvent atteindre des niveaux très élevés de débit et d’IOPS (requêtes d’E/S par seconde). En raison de la surcharge des API Win32, même si la bande passante de stockage disponible peut être utilisée, en tirer parti peut entraîner une utilisation du processeur inacceptablement élevée. C’est particulièrement vrai lorsque la charge de travail se compose d’un grand nombre de petites requêtes. Les API DirectStorage sont conçues pour éliminer la majeure partie de la surcharge du système d’exploitation en interagissant étroitement avec le matériel NVMe sous-jacent. Cela permet d’obtenir une bande passante plus élevée avec une utilisation moindre du processeur. L’objectif est de permettre le traitement de jusqu’à 50 000 requêtes par seconde en utilisant au maximum 10 % d’un seul cœur de processeur.Problèmes existants
Le contenu des jeux est de plus en plus volumineux, à mesure que le besoin de ressources en plus haute résolution augmente à chaque génération de consoles. Le matériel et les logiciels XBOX One existants présentent plusieurs limitations qui entravent la capacité des développeurs à transférer les données du disque dur vers la mémoire pour ce contenu de nouvelle génération.-
Utilisation élevée du processeur
- Les API Win32 existantes peuvent nécessiter un cœur de processeur entier en surcharge.
- Cela dépend du nombre de requêtes du titre.
-
Bande passante maximale insuffisante du disque
- Les livres blancs Optimisation des performances des fichiers sur XBOX Series X|S (rubrique sous NDA) et Optimisation des performances des fichiers sur XBOX One (rubrique sous NDA) abordent ce point en détail.
-
Impossibilité de hiérarchiser les requêtes disque
- Sans la possibilité de hiérarchiser les requêtes du titre, il peut être difficile de créer un système de streaming réactif.
-
Impossibilité d’annuler les requêtes disque
- Sans la possibilité d’annuler des requêtes, il peut être difficile de créer un système de lecture spéculative.
-
Absence de décompression accélérée par le matériel
- Sans décompression accélérée par le matériel, la décompression logicielle peut consommer beaucoup de ressources processeur.
Utilisation du processeur
Le principal objectif de conception de DirectStorage est de permettre à un titre de soutenir 50 000 IOPS en n’utilisant qu’entre 5 % et 10 % d’un seul cœur de processeur. Cela permet au titre d’atteindre la bande passante maximale du sous-système de stockage NVMe, tout en permettant d’utiliser le processeur pour d’autres besoins du titre. DirectStorage ajoute également la prise en charge de la décompression matérielle. Chaque requête de lecture peut être acheminée directement du lecteur NVMe vers le bloc de décompression matérielle intégré. Le titre n’a ainsi plus besoin de consacrer des ressources processeur à la décompression.Modèle de pipeline en file d’attente
DirectStorage utilise une méthode par lots, dans laquelle plusieurs requêtes sont ajoutées à une file d’attente. Plus tard, la file d’attente est vidée vers l’étape suivante du pipeline. Cela réduit immédiatement le coût processeur global d’une transition entre les étapes du pipeline. Avec l’ensemble d’API Win32 existant, il y a une transition pour chaque requête. Les files d’attente DirectStorage utilisent des algorithmes sans verrou pour réduire la contention. Le titre contrôle le moment où chaque file d’attente est vidée. Dans de nombreux cas avec les API Win32, les données du disque doivent être copiées dans une autre mémoire tampon. Dans certains cas, les données doivent être copiées plusieurs fois. DirectStorage élimine ce problème en mappant la mémoire tampon de destination fournie par le titre directement dans chaque couche du pipeline. Le matériel écrit directement dans la mémoire tampon fournie par le titre. Ces modifications contribuent à réduire considérablement la surcharge du processeur.Décompression
La capacité de décompression des données par le matériel a été augmentée. Il peut désormais gérer une plus grande variété de formats, à une vitesse supérieure à celle à laquelle le sous-système NVMe peut fournir les données. De plus, DirectStorage prend en charge la décompression sur place, ce qui évite d’avoir à gérer des mémoires tampons distinctes pour les données compressées et décompressées. Le matériel prend en chargeBCPACK et DEFLATE, et offre la possibilité de swizzler le
contenu final. Ces formats ne s’excluent pas mutuellement. Il est possible d’appliquer
les trois aux données. Le titre peut ainsi choisir la
méthode qui lui offre le meilleur taux de compression et les meilleures performances. Différentes ressources
peuvent utiliser différents paramètres de compression et de swizzle.
Profondeur de file d’attente
La recommandation précédente était de ne maintenir que 12 à 16 requêtes asynchrones en cours à la fois sur un disque rotatif. Aller au-delà n’apportait aucun gain de performances, et rester en deçà nuisait considérablement aux performances. Les titres devaient donc effectuer un travail supplémentaire pour équilibrer leurs requêtes de lecture en attente afin de rester dans la cible recommandée. Avec l’objectif de DirectStorage de permettre à un titre d’atteindre 50 000 IOPS, notre recommandation a changé. Le titre n’a plus besoin d’essayer d’équilibrer le travail en attente et la profondeur de file d’attente. Le titre doit soumettre toutes ses requêtes en attente. Il n’y a aucun avantage à retenir certaines requêtes. Dans de nombreux cas, retenir des requêtes peut nuire aux performances, car le matériel est bloqué en attente de nouvelles requêtes. Le système d’exploitation doit toujours fractionner les grandes requêtes de lecture en plusieurs requêtes plus petites dans certains cas (par exemple, pour gérer la fragmentation du disque). Toutefois, cela a été pris en compte dans l’architecture de DirectStorage. L’objectif de conception de 50 000 IOPS est basé sur le nombre d’opérations d’E/S du titre et non sur les requêtes finales envoyées au matériel.Notification
Dans l’architecture Win32, une surcharge importante est consacrée aux notifications d’achèvement de lecture. Le titre peut interroger une structure OVERLAPPED, attendre un handle d’événement associé ou effectuer une lecture bloquante synchrone. Globalement, cela augmente les besoins en ressources de chaque requête de lecture. DirectStorage conserve les deux concepts asynchrones de notification tout en ajoutant une troisième méthode. Les lectures bloquantes synchrones ne sont pas prises en charge dans DirectStorage ; un titre peut implémenter son propre système. Toutefois, cela n’est pas recommandé. La première méthode asynchrone est implémentée au moyen d’un bloc d’état défini lorsque les requêtes associées sont terminées. Le titre peut interroger le bloc si nécessaire pour déterminer quand les lectures sont terminées. Cela est similaire à la méthode Win32 consistant à interroger une structure OVERLAPPED pour détecter l’achèvement. La deuxième méthode asynchrone utilise un objetEvent Windows pour signaler l’achèvement.
Cela est similaire à l’utilisation d’une structure OVERLAPPED avec un objet Event correspondant.
Le titre peut utiliser la méthode WaitForSingleObject
pour suspendre le thread appelant jusqu’à ce que les opérations de lecture soient terminées.
La troisième méthode asynchrone est implémentée à l’aide d’un ID3D12Fence. Le titre
peut se suspendre en attendant la clôture ou interroger la clôture si nécessaire.
Il y a également l’avantage que le GPU peut utiliser la clôture pour être directement notifié
des requêtes terminées.
Le système de notification de DirectStorage n’est pas lié à une seule requête de lecture. Il s’agit
d’une entrée placée dans la file d’attente, qui est signalée lorsque toutes les requêtes de lecture
précédentes sont terminées. Le titre contrôle ainsi la granularité
dont il a besoin pour les notifications. Les notifications sont toujours signalées dans l’ordre de la file d’attente.
La file d’attente peut être considérée comme une file FIFO (premier entré, premier sorti). Le titre n’a
besoin d’interroger que la dernière notification pertinente. Toutes les requêtes précédemment mises en file d’attente
sont assurément terminées.
Décompression de mémoire à mémoire
DirectStorage fournit un type de file d’attente permettant d’appeler le matériel de décompression avec la mémoire comme source de décompression au lieu d’un fichier sur disque. Cela permet d’utiliser le matériel de décompression si la ressource compressée ne provient pas d’un fichier ou si elle a été précédemment chargée et conservée en mémoire comme cache. Une file d’attente à source mémoire n’accepte que des requêtes à source mémoire, et une file d’attente à source fichier n’accepte que des requêtes à source fichier. Si aucune option de décompression n’est spécifiée dans une requête à source mémoire, le matériel de décompression peut également servir de moteur de copie DMA. Bien que DirectStorage garantisse que les notifications d’achèvement sont dans l’ordre, DirectStorage ne fournit aucune garantie quant au moment où le traitement des requêtes commence. Par conséquent, il ne doit y avoir aucune dépendance de données entre les requêtes en attente. Autrement dit, la destination de la requête A ne peut pas servir de source à la requête B, sauf si la requête B est mise en file d’attente après l’achèvement de la requête A. Les files d’attente à source mémoire doivent être créées avec une priorité en temps réel. En outre, les requêtes en temps réel à source mémoire sont toujours traitées par le matériel de décompression avant les requêtes à source disque qui nécessitent une décompression. Si les files d’attente à source disque ne contiennent aucune requête de décompression, les deux types de files d’attente sont traités entièrement en parallèle sans affecter l’autre type.Priorité
DirectStorage permet d’attribuer un niveau de priorité à chaque file d’attente. Chaque entrée de la file d’attente hérite de la priorité de la file. Quatre niveaux de priorité différents sont fournis : temps réel, haute, normale et basse. Les requêtes sont traitées selon un tourniquet pondéré. Par exemple, traiter X requêtes de priorité haute avant de traiter une requête de priorité normale. Traiter Y requêtes de priorité normale avant de traiter une requête de priorité basse. La pondération de priorité est calculée en fonction de la taille de chaque requête. La pondération par défaut entre chaque priorité est d’environ 10x. Cela signifie que pour chaque 1 Ko de requête de priorité basse, 10 Ko de requête de priorité moyenne et 100 Ko de requête de priorité haute auront été traités. Les requêtes de lecture Win32 existantes sont acheminées via le même système de priorité. Toutes les requêtes Win32 sont considérées comme de priorité normale. Les files d’attente à source mémoire doivent être créées avec une priorité en temps réel.Annulation
Chaque requête de lecture DirectStorage est associée à un masque de 64 bits fourni par le titre. Cela permet de prendre en charge l’annulation des requêtes de lecture en attente. Le titre peut annuler les requêtes qui correspondent à un ensemble spécifique d’indicateurs dans le masque. Même avec la prise en charge de l’annulation, il est toujours possible qu’une requête de lecture soit traitée par le matériel. La demande d’annulation du titre est une tentative au mieux. Si la requête est déjà en cours de traitement actif par le matériel, elle ne peut pas être annulée. Comme la demande d’annulation est effectuée au mieux, le titre doit attendre d’être notifié que le traitement de la requête de lecture est terminé. Le titre ne peut libérer aucune des ressources nécessaires avant la réception d’une notification ultérieure dans la file d’attente. Toutefois, pendant ce temps, de nouvelles requêtes correspondant aux indicateurs utilisés dans une demande d’annulation précédente peuvent être mises en file d’attente et ne seront pas annulées. Lorsqu’une requête annulée se termine, elle est considérée comme une réussite, même si elle a été annulée et n’a pas produit le résultat complet. Autrement dit, si une annulation est tentée sur une requête, le titre ne peut plus consommer le résultat de la requête potentiellement annulée lors de son achèvement.Garantie
Les consoles XBOX One et XBOX One S offraient une garantie minimale de 40 Mo/s. La console XBOX One X a augmenté la garantie minimale à 60 Mo/s. Ces chiffres sont bien inférieurs aux limites réelles du matériel, qui se situent autour de 130 Mo/s. Cela était entièrement dû à la surcharge causée par le système d’exploitation. DirectStorage élimine la majeure partie de la surcharge causée par le système d’exploitation. Cela permet une garantie minimale plus proche des limites du matériel. La nouvelle garantie minimale de performances est de 2,0 Go/s sur une fenêtre de 250 ms pour les données brutes. L’utilisation de la décompression sur le contenu augmente encore la bande passante finale. Les futures consoles XBOX prendront en charge l’ajout de disques dynamiques installables par l’utilisateur également basés sur NVMe. La même garantie minimale de performances que celle fournie pour le disque interne s’applique également au disque installable par l’utilisateur.Vue d’ensemble de l’API
Les interfaces DirectStorage suivent le même modèle que les interfaces Direct3D. Le titre obtient d’abord une fabrique singleton. La fabrique sert à créer des files d’attente de requêtes et à ouvrir des fichiers ; chacun de ces objets est mappé directement au matériel. Les requêtes individuelles sont ensuite mises en file d’attente pour être soumises au matériel.IDStorageFactoryX
IDStorageFactoryX est l’interface principale permettant de créer des files d’attente, d’ouvrir des fichiers et de
soumettre les requêtes en attente.
L’objet IDStorageFactoryX possède les méthodes suivantes.
-
OpenFile
- Crée un objet
IDStorageFileX, qui représente un fichier.
- Crée un objet
-
CreateQueue
- Crée un objet
IDStorageQueueX. Sert à créer des requêtes de lecture.
- Crée un objet
-
CreateStatusArray
- Crée un objet
IDStorageStatusArray, qui gère les indicateurs d’état d’achèvement.
- Crée un objet
-
SetCPUAffinity
- Limite le travail de DirectStorage effectué hors du thread appelant à un ensemble de cœurs de processeur défini par le titre.
- REMARQUE DirectStorage tente d’effectuer la majeure partie du travail dans
le thread appelant. Le travail hors du thread appelant n’a lieu que lorsqu’il ne peut pas être
effectué dans le thread appelant. Voici quelques exemples.
- Le pipeline de ressources sous-jacent est plein pendant
IDStorageQueueX::Submit, et toutes les requêtes de la file d’attente ne peuvent pas être transmises. Les requêtes restantes seront traitées ultérieurement, lorsque les ressources se libéreront, et ce traitement est effectué dans un thread de travail DirectStorage. - Traitement de l’achèvement des requêtes dans
ID3DFenceouIDStorageStatusArray.
- Le pipeline de ressources sous-jacent est plein pendant
-
SetDebugFlags
- Détermine si DirectStorage effectue des validations supplémentaires lors de la mise en file d’attente des requêtes afin de faciliter le débogage.
-
SetStagingBufferSize
- Définit la taille de la mémoire tampon intermédiaire utilisée pour stocker temporairement le contenu chargé depuis le périphérique de stockage avant son déchiffrement/sa décompression. Si seules des files d’attente à source mémoire sont utilisées, la mémoire tampon intermédiaire peut avoir une taille de 0.
IDStorageFactoryX1
L’interfaceIDStorageFactoryX1 étend l’interface IDStorageFactoryX avec
une méthode GetStats.
- GetStats
- Obtient les statistiques de DirectStorage. Cette fonction peut servir à intégrer DirectStorage aux pipelines de diagnostic et de télémétrie existants. Elle effectue un traitement minimal et peut donc être appelée fréquemment. Les statistiques n’incluent pas les opérations d’E/S de fichiers Win32.
IDStorageFactoryX2
L’interfaceIDStorageFactoryX2 étend l’interface `IDStorageFactoryX1 avec
une méthode CreateQueue1.
- CreateQueue1
- Crée un objet
IDStorageQueueX2. Utilise une nouvelle structure pour permettre des options supplémentaires lors de la création de la file d’attente, notamment la possibilité de remplacer la fonctionnalité de soumission automatique d’une file d’attente.
- Crée un objet
IDStorageFileX
Tous les fichiers doivent d’abord être ouverts par DirectStorage via l’objetIDStorageFactoryX. Cela équivaut à utiliser CreateFile dans les interfaces
d’API Win32.
Les fichiers sont ouverts avec l’autorisation FILE_SHARED_READ. Si nécessaire, les titres peuvent
ouvrir simultanément le fichier à l’aide des API Win32, à condition de respecter les
autorisations appropriées. Pendant le développement, les déploiements libres et empaquetés sont pris en charge.
Les fichiers sont fermés soit en appelant explicitement la fonction Close
sur l’objet fichier, soit lorsque la dernière référence à l’objet
IDStorageFileX correspondant est libérée. Toutefois, toutes les opérations d’E/S en attente doivent
être terminées avant que le fichier puisse être fermé. Cela signifie que les deux méthodes de fermeture
d’un fichier bloquent jusqu’à ce que toutes les opérations d’E/S en attente sur ce fichier soient terminées.
Le jeu peut obtenir un handle Win32 vers un fichier représenté par un objet IDStorageFileX
en appelant la fonction GetHandle.
Le handle est ouvert avec les autorisations GENERIC_READ et le mode de partage FILE_SHARE_READ.
Il peut servir à interroger la taille du fichier, entre autres. Le handle doit être fermé
avec CloseHandle() lorsqu’il n’est plus nécessaire.
IDStorageQueueX
Les requêtes de lecture sont soumises au NVMe via un objetIDStorageQueueX.
Toutefois, les requêtes ne sont pas soumises au périphérique tant que le titre n’a pas appelé
Submit sur la file d’attente, ou que l’une des méthodes Enqueue
n’a pas rempli plus de la moitié de la capacité de la file d’attente depuis la dernière soumission, déclenchant
ainsi une soumission automatique. Les soumissions sont traitées comme une seule transition vers
l’étape suivante du pipeline. Le titre peut ainsi contrôler le moment où le coût processeur
de la transition entre le titre et le noyau est engagé.
Les objets IDStorageQueueX possèdent quatre propriétés.
-
SourceType
- Indique si la file d’attente peut recevoir des requêtes à source fichier ou des requêtes à source mémoire.
-
Priority
- Priorité de toutes les requêtes soumises à la file d’attente : temps réel, haute, normale ou basse.
- Les files d’attente à source mémoire doivent être créées avec une priorité en temps réel.
- Les requêtes sont traitées selon un tourniquet pondéré basé sur la priorité.
- Les requêtes Win32 sont traitées au niveau de priorité normal.
-
Capacity
- Nombre maximal de requêtes en attente que la file d’attente peut contenir.
- Toute tentative de mise en file d’attente d’une requête lorsque la file d’attente est pleine bloque jusqu’à ce que des entrées soient terminées par le matériel.
- La quantité de mémoire nécessaire pour une file d’attente correspond approximativement à la capacité de la file d’attente
multipliée par la taille d’une
DSTORAGE_REQUEST.
-
Name
- Cette propriété sert uniquement à faciliter le débogage. Le nom n’est utilisé par aucune partie du code DirectStorage, mais il est visible dans les outils de développement, comme PIX (rubrique sous NDA).
IDStorageQueueX1
L’interfaceIDStorageQueueX1 étend l’interface IDStorageQueueX avec la
méthode EnqueueSetEvent.
IDStorageQueueX2
L’interfaceIDStorageQueueX2 étend l’interface `IDStorageQueueX1 avec la méthode CreateQueue1.
Ainsi qu’une propriété supplémentaire.
- Options
- Indicateurs utilisés pour contrôler le comportement de la file d’attente, notamment la désactivation de la soumission automatique.
EnqueueRequest
Cette interface est fonctionnellement identique à l’interface Win32ReadFile.
Des requêtes de lecture individuelles sont créées et soumises à la file d’attente. La principale
différence est que DirectStorage permet de mettre en file d’attente de nombreuses requêtes avant
la soumission, prend en charge la décompression matérielle et prend en charge l’annulation.
Les requêtes possèdent plusieurs propriétés principales.
Source de la requête
Selon la combinaison de Options.SourceType et
Options.SourceIsPhysicalPages, DirectStorage utilise l’un des trois
groupes de propriétés suivants pour indiquer l’emplacement des données sources.
-
File et FileOffset
- Ce groupe est utilisé lorsque
Options.SourceTypea la valeurDSTORAGE_REQUEST_SOURCE_FILE - File est préalablement ouvert avec
IDStorageFactoryX::OpenFile. - FileOffset doit être aligné sur 16 octets si la décompression est utilisée, ou
n’a aucune exigence d’alignement si la décompression n’est pas utilisée.
- Il s’agit d’un changement majeur par rapport à Win32, qui exigeait un alignement de 4 Kio dans le fichier pour les lectures asynchrones.
- Ce groupe est utilisé lorsque
-
Source
- Ce groupe est utilisé lorsque
Options.SourceTypea la valeurDSTORAGE_REQUEST_SOURCE_MEMORYetOptions.SourceIsPhysicalPagesa la valeurFALSE. - Mémoire tampon contenant les données à décompresser.
- Ce groupe est utilisé lorsque
-
SourcePageArray et SourcePageOffset
- Ce groupe est utilisé lorsque
Options.SourceTypea la valeurDSTORAGE_REQUEST_SOURCE_MEMORYetOptions.SourceIsPhysicalPagesa la valeurTRUE. - Similaire à
Source, mais fournit la mémoire tampon source sous la forme d’un tableau de pages physiques de 64 Ko et du décalage en octets dans la première page. - Les pages physiques de 64 Ko peuvent être allouées par
XMemAllocatePhysicalPages.
- Ce groupe est utilisé lorsque
- Taille, en octets, des données sources à lire, à partir d’une mémoire tampon ou d’un fichier.
- Lorsque les décompressions
zlibetBCPACKsont toutes deux activées dans cette requête,IntermediateSizesert à indiquer la taille intermédiaire vers laquelle les données sources sont décompressées par zlib (et à partir de laquelle elles sont décompressées par BCPACK). - Sinon, cette propriété doit être définie sur 0.
Options.DestinationIsPhysicalPages, DirectStorage utilise l’un des
deux groupes de propriétés suivants pour indiquer l’emplacement de la destination.
-
Destination
- Ce groupe est utilisé lorsque
Options.DestinationIsPhysicalPagesa la valeurFALSE. - Mémoire tampon de destination des données chargées finales.
- La décompression s’effectue à l’aide de mémoires tampons internes partagées et peut être considérée comme effectuée sur place.
- Ce groupe est utilisé lorsque
-
DestinationPageArray et DestinationPageOffset
- Ce groupe est utilisé lorsque
Options.DestinationIsPhysicalPagesa la valeurTRUE. - Similaire à
Destination, mais fournit la mémoire tampon de destination sous la forme d’un tableau de pages physiques de 64 Ko et du décalage en octets dans la première page. - Les pages physiques de 64 Ko peuvent être allouées par
XMemAllocatePhysicalPages.
- Ce groupe est utilisé lorsque
- Taille attendue, en octets, du contenu chargé final. La destination doit disposer d’un espace suffisant pour l’opération.
- La taille doit être égale à SourceSize lorsque la décompression n’est pas utilisée, ou supérieure à SourceSize lorsque la décompression est utilisée.
- Balise arbitraire de 64 bits définie par le titre.
- Cette balise sert de masque pour les demandes d’annulation.
- Chaîne facultative destinée à faciliter le débogage. Le nom peut apparaître dans les outils
de développement, comme PIX (rubrique sous NDA), ou dans l’enregistrement d’erreur
obtenu à partir de
IDStorageQueueX::RetrieveErrorRecord. La chaîneNamedoit rester accessible pendant toute la durée de vie de la requête.
- ZlibDecompress
- Indique que les données doivent être décompressées à l’aide de la norme de décompression RFC 1950.
- BcpackMode
- Indique le mode
BCPACKà utiliser pour décompresser les données. - None est une option valide et signifie que les données ne sont pas compressées avec
BCPACK.
- Indique le mode
- SwizzleMode
- Indique comment les données finales doivent être swizzlées en mémoire.
- DestinationIsPhysicalPages
- Indique que la mémoire tampon de destination est spécifiée à l’aide de DestinationPageArray et DestinationPageOffset au lieu de Destination.
- SourceType
- Une requête peut être à source mémoire, et possède alors les propriétés
Source/SourcePageArrayetSourcePageOffset, ou à source fichier, et possède alors les propriétésFile/FileOffset.
- Une requête peut être à source mémoire, et possède alors les propriétés
- SourceIsPhysicalPages
- Indique que la mémoire tampon source est spécifiée à l’aide de SourcePageArray et SourcePageOffset au lieu de Source.
EnqueueStatus/EnqueueSignal/EnqueueSetEvent
Les requêtes peuvent être mises en file d’attente et traitées comme une série de requêtes liées. Pour ce faire, on met en file d’attente une notification lorsque le traitement atteint un certain point de la file d’attente. Les notifications ne sont traitées que lorsque toutes les requêtes de lecture précédentes sont terminées. Cela garantit que les données de toutes les requêtes précédentes sont immédiatement disponibles. Le titre dispose de deux méthodes d’interrogation et d’une méthode d’attente pour les notifications. Le titre peut insérer un objetID3D12Fence, un objet IDStorageStatusArrayX ou une
opération de définition d’événement. L’objet ID3D12Fence se comporte comme prévu pour un objet
ID3D12Fence. Un thread du titre peut attendre un Event, le processeur peut interroger la clôture et le
GPU peut interroger la clôture. L’objet IDStorageStatusArrayX permet au processeur d’interroger
l’achèvement et d’accéder aux éventuels échecs de lecture. La méthode EnqueueSetEvent
permet à un thread du titre d’attendre l’événement spécifié au lieu d’effectuer une interrogation.
Cela diffère de ID3D12Fence::SetEventOnCompletion, car l’implémentation XBOX
de ID3D12Fence::SetEventOnCompletion tourne en boucle sur la clôture jusqu’à ce qu’elle soit signalée,
consommant ainsi le thread matériel du processeur jusqu’au signal, tandis que EnqueueSetEvent
permet au thread du titre d’utiliser WaitForSingleObject/WaitForMultipleObjects pour
céder le processeur à d’autres threads jusqu’à ce que l’événement soit signalé.
Comme indiqué précédemment, toutes les requêtes sont terminées dans l’ordre, même si le matériel
sous-jacent décide de les réorganiser pour des raisons de performances. Les notifications ne sont pas signalées
tant que toutes les requêtes précédemment mises dans la file d’attente ne sont pas terminées.
Décompression
La décompression est gérée par un matériel dédié. Cela élimine la surcharge processeur des algorithmes de décompression traditionnels. DirectStorage alloue un bloc fixe de mémoire lors de l’initialisation pour l’utiliser comme mémoire tampon de travail pour la décompression. Cela permet la décompression sur place et évite de devoir conserver en mémoire à la fois les données compressées et décompressées. Le matériel de décompression prend en charge trois modes de fonctionnement. Les modes ne s’excluent pas mutuellement ; toute combinaison de modes peut donc être spécifiée. Les modes de décompression sont appliqués dans l’ordre suivant :DEFLATE, BCPACK et Swizzle.
-
ZLibDecompress- Il s’agit de la norme de compression IETF RFC 1950.
-
BCPackBCPackest un codeur entropique personnalisé conçu spécifiquement pour les données BCn. En général, cela signifie que les points de terminaison de couleur sont séparés des indices de palette (c’est-à-dire des poids) et compressés à l’aide d’un algorithme rANS.
-
Swizzle- Les modes
Swizzleet shuffle peuvent apporter des optimisations supplémentaires dans les pipelines de contenu.
- Les modes
Mémoire tampon intermédiaire
DirectStorage utilise en interne une mémoire tampon pour placer tout le contenu lu à partir du stockage NVMe brut avant d’effectuer des opérations telles que le déchiffrement et la décompression. Cette mémoire tampon intermédiaire permet au lecteur NVMe et au circuit de déchiffrement/décompression de fonctionner en pipeline, en parallèle. Sa taille par défaut est de 32 Mio et elle est allouée lorsque le premier pointeur de fabrique DirectStorage est récupéré. Si un titre utilise DirectStorage uniquement pour des opérations de décompression de mémoire à mémoire, la mémoire tampon intermédiaire n’est pas nécessaire, et SetStagingBufferSize peut être appelée pour définir la taille de la mémoire tampon intermédiaire sur 0. La version actuelle de DirectStorage prend en charge des tailles de mémoire tampon intermédiaire de 0, 16, 20, 24, 28 et 32 Mio. Une taille inférieure à la valeur par défaut permet au titre d’économiser de la mémoire, mais peut affecter les performances de lecture globales. REMARQUE : SetStagingBufferSize ne peut être appelée que lorsqu’il n’existe aucun objetIDStorageQueueX ni aucun objet IDStorageFileX ; sinon, elle lève une erreur.
CancelRequestsWithTag
DirectStorage prend en charge l’annulation des requêtes. Chaque requête est associée à une balise de 64 bits définie par le titre. Elle sert de masque de bits pour déterminer les requêtes à annuler. Le titre fournit un masque et une valeur pour l’annulation. La file d’attente tente d’annuler toutes les requêtes qui correspondent au critère :tag & mask == value.
L’annulation est une opération effectuée au mieux. Selon l’emplacement de la requête dans
le pipeline, il peut être impossible de l’annuler. Par exemple, la requête peut
être en cours de décompression par le matériel, ce qui ne peut pas être annulé. L’API
retourne immédiatement et ne bloque pas en attendant que toutes les requêtes annulées soient
traitées. Les titres doivent attendre qu’une notification ultérieure dans la file d’attente soit signalée
avant de libérer les ressources associées aux requêtes annulées.
Veillez à ne pas ajouter de requêtes à une file d’attente au moment même où
CancelRequestsWithTag est appelée.
Dans ce cas, le comportement n’est pas défini. Toutefois, les requêtes ajoutées à la file d’attente
après le retour de l’appel à CancelRequestsWithTag
ne seront pas annulées, même si elles correspondent aux critères. Seules les requêtes précédemment
mises en file d’attente seront annulées.
GetErrorEvent/RetrieveErrorRecord
Si une lecture provoque une erreur, cette lecture est marquée comme terminée. Les notifications ultérieures de la file d’attente ne sont pas empêchées d’être signalées. La notification des erreurs est gérée par un objetEvent associé à la file d’attente, qui peut être
récupéré avec GetErrorEvent. Le titre peut
utiliser WaitForSingleObject sur l’objet Event renvoyé par
GetErrorEvent. Si l’événement est signalé,
le titre peut obtenir la première erreur survenue depuis le dernier appel à
la fonction RetrieveErrorRecord
en appelant la fonction RetrieveErrorRecord.
L’enregistrement d’erreur renvoyé par
RetrieveErrorRecord contient uniquement les données
de la première requête ayant échoué dans la file d’attente depuis le dernier appel à
RetrieveErrorRecord. Les données de
l’enregistrement d’erreur ne sont pas définies si l’objet Event d’erreur n’est pas signalé ou si les données ont
déjà été récupérées.
Query
Obtient des informations sur la file d’attente. Cela inclut les structures DSTORAGE_QUEUE_DESC ou DSTORAGE_QUEUE_DESC1 utilisées pour la création de la file d’attente, ainsi que le nombre d’emplacements vides et le nombre d’entrées à mettre en file d’attente pour déclencher la soumission automatique.Meilleures pratiques
Les mêmes conseils de meilleures pratiques donnés pour Win32 s’appliquent également à DirectStorage. Les seuils de performances optimales ont radicalement changé.Tailles de lecture
Le conseil d’origine pour un disque rotatif était de lire par blocs d’au moins 128 Kio. Les performances continuent d’augmenter avec des tailles de bloc plus importantes. Les blocs de 512 Kio offraient les meilleures performances. L’absence de pièces mobiles sur le NVMe crée un seuil beaucoup plus bas. Les performances de lecture font un bond important à partir des lectures de 32 Kio et commencent à plafonner à 64 Kio. Lire au-delà n’augmente pas les performances. Cela signifie qu’il faut moins d’efforts pour fusionner les données en blocs plus importants dans le package afin d’obtenir des performances optimales. Au-delà de 512 Kio, si la décompression est utilisée, préférez des requêtes plus petites mais plus nombreuses en parallèle à une seule requête volumineuse. Une seule requête volumineuse oblige la décompression à être sérialisée, tandis que plusieurs requêtes simultanées permettent à plusieurs unités matérielles de décompression de fonctionner en parallèle et d’atteindre le débit maximal. Dans la version d’octobre 2022 du Microsoft Game Development Kit (GDK), la taille maximale d’une requête unique est passée de 32 Mio pour la destination à 1 Gio d’utilisation mémoire combinée (source plus destination). Cela permet de faciliter le portage depuis d’autres API de stockage qui autorisent de grandes tailles de lecture. Mais la recommandation de taille précédente pour atteindre le débit maximal reste la même, car les requêtes volumineuses ne permettent pas à plusieurs unités matérielles de décompression de fonctionner en parallèle.Ordre
Auparavant, avec les disques rotatifs, des efforts étaient consacrés à l’ordonnancement de l’emplacement des lectures sur le disque. La situation idéale consistait à lire à partir d’emplacements séquentiels sur le disque. Cela réduisait au minimum les mouvements de la tête du disque, ce qui éliminait le temps de recherche comme facteur. Cela pouvait augmenter les performances d’un ordre de grandeur. Il était même avantageux de soumettre des lectures aléatoires triées par emplacement, ce qui était dans certains cas 2 fois plus rapide. Il reste utile d’ordonner les requêtes de lecture de manière aussi séquentielle que possible sur un lecteur NVMe. Le lecteur NVMe lit par blocs alignés sur 64 Kio. De ce fait, de la bande passante peut être gaspillée à lire des sections inutilisées d’un bloc de 64 Kio. Si une requête de lecture ne porte que sur 4 Kio, 60 Kio de bande passante seront gaspillés. Le NVMe réutilise ces 60 Kio supplémentaires pour satisfaire d’autres requêtes en attente si possible. Par exemple, si vous avez deux lectures séquentielles, une de 32 Kio suivie d’une de 8 Kio, il n’y a toujours qu’une seule lecture de 64 Kio depuis le lecteur.Gestion des files d’attente
La recommandation pour les disques rotatifs était d’avoir une taille de file d’attente comprise entre 12 et 16. Une profondeur de file d’attente plus importante n’apportait aucun avantage, et la baisse de performances liée à l’utilisation de profondeurs plus faibles était importante. La spécification NVMe indique qu’un lecteur NVMe doit prendre en charge plusieurs files d’attente d’une profondeur allant jusqu’à 65 536 entrées par file. DirectStorage prend en charge cette exigence, ce qui permet au titre de soumettre des milliers de requêtes à la fois. Auparavant, avec les disques rotatifs, les titres mettaient en mémoire tampon les requêtes en attente pour maintenir une profondeur de file d’attente comprise entre 12 et 16. La recommandation pour DirectStorage est de ne pas mettre en mémoire tampon les requêtes, mais de les mettre en file d’attente dès leur création. L’ensemble du système est un pipeline, et la mise en mémoire tampon par le titre crée des bulles dans le pipeline, ce qui peut nuire gravement aux performances. Une autre recommandation consiste à créer une file d’attente d’une capacité au moins quatre fois supérieure au plus grand nombre de requêtes créées par image. Cela devrait offrir une capacité suffisante pour que de nouvelles requêtes puissent être ajoutées sans blocage en attendant que les requêtes existantes se terminent.Gestion des notifications
En général, moins il y a de requêtes de notification ajoutées à la file d’attente, mieux c’est. La recommandation est de trouver un équilibre entre les besoins du titre et la réduction au minimum des requêtes de notification mises en file d’attente. Mettre en file d’attente une notification après chaque requête ne fait que nuire aux performances globales en raison de la surcharge accrue liée au traitement de ces notifications. Un exemple consisterait à regrouper par contenu. Par exemple, les textures SFS, les besoins du terrain (par exemple, maillage et texture) et les besoins des acteurs (par exemple, maillage, texture et animation). Cela permet de lier une seule notification à la disponibilité de toutes les ressources nécessaires pour créer un objet. Le choix entre l’utilisation d’unID3D12Fence et celle d’un tableau d’état dépend
des besoins du titre. Les données doivent-elles être utilisées immédiatement par le GPU ? Est-il
acceptable que le thread de vérification se suspende jusqu’à la fin des lectures ? Une interrogation
périodique suffit-elle parce que les données ne peuvent être traitées qu’à certains moments de
l’image ?
Considérations
Avec la possibilité d’avoir un nombre beaucoup plus élevé de requêtes en cours, il faut veiller à ne pas créer de goulots d’étranglement dans d’autres parties du titre. Les coûts de gestion des requêtes par le titre pourraient rapidement dépasser les gains obtenus grâce à DirectStorage. La recommandation est d’examiner tout le code de prise en charge par requête et de déterminer ce qui peut être réduit. Chaque requête nécessite-t-elle l’allocation d’un nouveau bloc de mémoire ?- Le système de mémoire engendre une surcharge pour trouver un nouveau bloc et mettre à jour les listes internes.
- Envisagez de réutiliser les blocs de mémoire autant que possible.
- Cela crée davantage de contention à mesure que les mises à jour se multiplient.
- Envisagez d’adopter une approche sans verrou autant que possible.
- L’annulation est prise en charge, ce qui pourrait conduire à créer davantage de requêtes.
- Toutefois, de la mémoire doit être disponible pour la spéculation.
- Envisagez des limites strictes pour les seuils de spéculation.
