Vecteurs SoA sur SPU


8

J'ai beaucoup lu sur les avantages de l'organisation des données en `` structures de tableaux '' (SoA) au lieu du `` tableau de structures '' (AoS) typique pour obtenir un meilleur débit lors de l'utilisation d' instructions SIMD . Bien que le `` pourquoi '' ait un sens total pour moi, je ne sais pas combien faire pour travailler avec des choses comme des vecteurs.

Les vecteurs eux-mêmes peuvent être considérés comme une structure d'un tableau de données (de taille fixe), vous pouvez donc convertir un tableau de ces derniers en une structure de tableaux X, Y et Z. Grâce à cela, vous pouvez travailler sur 4 vecteurs à la fois, par opposition à un à la fois.

Maintenant, pour la raison spécifique que je poste sur GameDev:

Est-ce que cela a du sens pour travailler avec des vecteurs sur le SPU? Plus précisément, cela a-t-il un sens pour les tableaux DMA multiples juste pour un seul vecteur? Ou serait-il préférable de s'en tenir à DMAing le tableau de vecteurs et de les dérouler dans les différents composants avec lesquels travailler?

Je pouvais voir l'avantage de couper le déroulement (si vous l'avez fait `` AoS ''), mais il semble que vous pourriez rapidement manquer de canaux DMA si vous preniez cette route et travailliez avec plusieurs ensembles de vecteurs à la fois.

(Remarque: aucune expérience professionnelle avec Cell pour le moment, mais nous avons joué dans OtherOS pendant un certain temps)

Réponses:


5

Une approche consiste à utiliser une approche AoSoA (lire: Array of Struct of Array) qui est un hybride d'AoS et SoA. L'idée est de stocker N structures valant des données dans un bloc contigu sous forme SoA, puis les N structures suivantes valant sous forme SoA.

Votre formulaire AoS pour 16 vecteurs (étiquetés 0,1,2 ... F), accéléré à la granularité de 4 structures est:

000111222333444555666777888999AAABBBCCCDDDEEEFFF
XYZXYZXYZXYZXYZXYZXYZXYZXYZXYZXYZXYZXYZXYZXYZXYZ

pour SoA, c'est:

0123456789ABCDEF
XXXXXXXXXXXXXXXX

0123456789ABCDEF
YYYYYYYYYYYYYYYYY

0123456789ABCDEF
ZZZZZZZZZZZZZZZZ

pour AoSoA, cela devient:

01230123012345674567456789AB89AB89ABCDEFCDEFCDEF
XXXXYYYYZZZZXXXXYYYYZZZZXXXXYYYYZZZZXXXXYYYYZZZZ

L'approche AoSoA présente les avantages suivants de l'AoS:

  • Un seul transfert DMA est requis pour transférer un bloc de structures vers la mémoire locale du SPU.
  • Les structures ont toujours une chance que toutes les données s'ajustent dans une ligne de cache.
  • La prélecture de blocs est toujours très simple.

L'approche AoSoA présente également les avantages de la forme SoA:

  • Vous pouvez charger des données de la mémoire locale du SPU directement dans des registres vectoriels 128 bits sans avoir à parcourir vos données.
  • Vous pouvez toujours opérer sur 4 structures à la fois.
  • Vous pouvez utiliser pleinement la SIMD'ness de votre processeur vectoriel s'il n'y a pas de branchement de base (c.-à-d. Pas de voies inutilisées dans votre arithmétique vectorielle)

L'approche AoSoA présente encore certains de ces inconvénients de la forme SoA:

  • la gestion des objets doit être effectuée avec une granularité rapide.
  • les écritures à accès aléatoire d'une structure complète doivent désormais toucher la mémoire dispersée.
  • (ceux-ci peuvent s'avérer non problématiques selon la façon dont vous organisez / gérez vos structures et leur durée de vie)

BTW, ces concepts AoSoA s'appliquent très bien à SSE / AVX / LRBni, ainsi qu'aux GPU qui peuvent être comparés à des processeurs SIMD très larges, par exemple. 32/48/64 de large selon le fournisseur / l'architecture.


Je ne vois pas comment cela offre un avantage sur le fait de ne pas les emballer par composant, sauf si vous empaquetez des données non vectorielles que vous utilisez réellement comme flottants - bien que je vois que votre AoS exclut W, ce qui ne semble pas très convivial pour l'accès à la mémoire, je devinez dans ce cas il y a une victoire. Notez également que les SPU n'ont pas de lignes de cache, sauf pour communiquer avec la mémoire principale.
Kaj

2
1. Comme pour tout, votre kilométrage peut varier en fonction de vos données / algorithme / processeur exacts. Dans les cas de contraintes de registre, il peut être utile d'éviter d'avoir besoin de 4 registres temporaires avant de pouvoir mélanger tous vos champs X dans le même registre. Mais encore une fois, YMMV. 2. Ma réponse était plus générale car les concepts se transfèrent bien dans le domaine de la programmation parallèle de données; les considérations sur les lignes de cache sont plus pertinentes pour GPU / SSE mais je pensais que je devrais les mentionner tout de même :)
jpaver

1
Assez juste, je suis éclairé et apprendrai à critiquer plus subtilement! Merci d'avoir partagé vos idées: o)
Kaj

3

Les SPU sont en fait un cas spécial intéressant en ce qui concerne la vectorisation du code. Les instructions sont divisées en familles «arithmétique» et «chargement / stockage», et les deux familles s'exécutent sur des pipelines distincts. Le SPU peut émettre un de chaque type par cycle.

Le code mathématique est évidemment fortement lié par des instructions mathématiques - donc généralement les boucles mathy sur SPU auront beaucoup, beaucoup de cycles ouverts sur le tube de chargement / stockage. Étant donné que les brassages se produisent sur le tuyau de chargement / stockage, vous avez souvent suffisamment d'instructions de chargement / stockage gratuites pour passer du formulaire xyzxyzxyzxyz au format xxxxyyyyzzzz sans aucun frais généraux.

Cette technique est utilisée au moins chez Naughty Dog - voir leurs présentations d'assemblage SPU ( partie 1 et partie 2 ) pour plus de détails.

Malheureusement, le compilateur n'est souvent pas assez intelligent pour le faire automatiquement - si vous décidez de suivre cette voie, vous devrez soit écrire l'assemblage vous-même, soit dérouler vos boucles à l'aide d'intrinsèques et vérifier l'assembleur pour vous assurer que c'est ce que vous voulez. Donc, si vous cherchez à écrire du code multi-plateforme général qui fonctionne bien sur SPU, vous voudrez peut-être utiliser SoA ou AoSoA (comme le suggère jpaver).


Ah, nous sommes d'accord après tout: o) Swizzle sur le SPU si vous en avez besoin, suffisamment de temps pour le faire là-bas.
Kaj

1

Comme pour toute optimisation, profil! La lisibilité vient en premier et ne doit être sacrifiée que lorsque le profilage identifie un goulot d'étranglement particulier et que vous avez épuisé toutes vos options pour régler l'algorithme de haut niveau (le moyen le plus rapide de faire le travail est de ne pas avoir à faire le travail!) Vous devez toujours reprofiler en suivant toute optimisation de bas niveau pour confirmer que vous avez vraiment rendu les choses plus rapides que l'inverse, en particulier avec des pipelines aussi excentriques que ceux de la cellule.

Les techniques que vous utiliserez alors dépendront des détails du goulot d'étranglement. En général, lorsque vous travaillez avec des types vectoriels, un composant vectoriel que vous ignorez dans un résultat représente un gaspillage de travail. La commutation SoA / AoS n'a de sens que si elle vous permet de faire un travail plus utile en remplissant ces composants inutilisés (par exemple, un produit scalaire sur le PPU de la PS3 vs quatre produits scalaires en parallèle dans le même laps de temps). Pour répondre à votre question, passer du temps à mélanger les composants juste pour effectuer une opération sur un seul vecteur me semble être une pessimisation!

Le revers des SPU est que la majeure partie du coût des petits transferts DMA est en cours de configuration; rien de moins de 128 octets prendra le même nombre de cycles à transférer, et rien de moins d'environ un kilo-octet seulement quelques cycles de plus. Ne vous inquiétez donc pas du fait que DMA utilise plus de données que vous n'en avez strictement besoin; la réduction du nombre de transferts DMA séquentiels déclenchés et l'exécution de travaux pendant les transferts DMA - et donc le déploiement de prologues et d'épilogues de boucle pour former des pipelines logiciels - est la clé des bonnes performances du SPU, et il est plus facile de traiter les cas d'angle en récupérant des données supplémentaires / rejeter les résultats partiellement calculés plutôt que de sauter à travers des cerceaux pour essayer de déterminer la quantité exacte de données qui doivent être lues et traitées.


Si vous finissez par les déballer, selon l'approche AOSAO, tirez au moins plusieurs vecteurs à la fois. Vous souhaitez également extraire un lot et, lors du traitement, les extraire dans le lot suivant. Lors de l'envoi du premier lot, vous traitez le second et retirez le troisième. De cette façon, vous cachez autant de latence que possible.
Kaj

0

Non, cela n'aurait pas beaucoup de sens en général car la plupart des opcodes vectoriels opèrent sur un vecteur dans son ensemble et non sur des composants séparés. Vous pouvez donc déjà multiplier un vecteur en 1 instruction, alors qu'avec la séparation des composants séparés, vous dépenseriez 4 instructions dessus. Donc, comme vous effectuez essentiellement de nombreuses opérations en général sur une partie d'une structure, vous êtes préférable de les regrouper dans un tableau, mais vous ne faites presque jamais des choses uniquement sur un composant d'un vecteur, ou très différentes sur chaque composant, donc les casser out ne fonctionnerait pas.
Bien sûr, si vous trouvez une situation où vous devez faire quelque chose uniquement avec (disons) les composants x des vecteurs, cela pourrait fonctionner, mais la pénalité de tout renvoyer lorsque vous avez besoin du vecteur réel ne serait pas bon marché, donc vous pourriez Je me demande si vous ne devriez pas utiliser de vecteurs pour commencer, mais juste un tableau de flottants qui permettent aux opcodes vectoriels de faire leurs calculs spécifiques.


2
Vous manquez le point de SoA pour les mathématiques vectorielles. Vous n'avez rarement qu'un seul objet sur lequel vous travaillez - en pratique, vous itérez un tableau et faites la même chose pour de nombreux objets. Pensez à faire 4 produits scalaires. Si vous stockez des vecteurs au format AoS sous la forme xyz0, prendre le point de deux vecteurs nécessite multiplier-shuffle-add-shuffle-add - 5 instructions. Faire 4 produits dot nécessite 20 instructions. En revanche, si vous avez 8 vecteurs stockés en mode SoA (xxxx, yyyy, zzzz, xxxx, yyyy, zzzz), vous pouvez créer 4 produits scalaires avec seulement 3 instructions (mul, madd, madd) - c'est 6 fois plus rapide.
Charlie

Bon point. Cependant, deux observations. Je garderais toujours le W présent pour ne pas avoir besoin de 20 instructions, deuxièmement, la plupart des frais généraux restants peuvent être cachés dans la latence des autres instructions - votre boucle serrée souffrirait de graves blocages de pipeline, non? faire les 6 fois est une optimisation théorique. Donc, même si oui, vous voulez regrouper vos opérations - vous n'aurez presque jamais besoin de faire un lot rapide de produits scalaires sans rien d'autre à faire sur lesdites données. Le coût du démoulage / dispersion du côté PPU serait trop un sacrifice pour moi.
Kaj

Grognement, je suis corrigé - sur SPU j'aurais besoin de 20 si cela était fait naïvement (mais je mélangerais en place). C'est l'une des choses où j'ai fini par faire beaucoup de swizzles pour le rendre optimal. 360 a un joli point intrinsèque (mais n'a pas la manipulation de bits impressionnante).
Kaj

Ouais, maintenant que j'y pense, si vous essayez de faire des "produits à 4 points", vous pouvez faire mieux que 20 instructions, car vous pouvez combiner certains des ajouts ultérieurs. Mais avoir vos vecteurs dans les registres xxxx, yyyy, zzzz - que vous ayez swizzled ou stocké en tant que SoA - se débarrasse complètement de ces mélanges. Quoi qu'il en soit, vous avez raison de dire que SoA rend le code de logique branchée plus lent - mais je dirais que la solution dans de nombreux cas comme celui-ci est de regrouper vos données et de refactoriser la logique branchée en de belles boucles plates.
Charlie

D'accord. Je suis assez sûr que si je passe en revue mon ancien code SPU (impossible, entreprise précédente), il y a des cas où je l'ai déplacé au format xxxxyyyyzzzz pour l'optimisation sans le réaliser spécifiquement. Je ne l'ai jamais proposé depuis le PPU dans ce format. Remarquez, OP ce que vous envisagez de dma-ing x, y, z séparément. Cela ne fonctionnerait certainement pas pour moi. Je préfèrerais aussi (comme je le faisais) localement car tout ne fonctionne pas mieux au format xxxxyyyyzzzz. Je dois choisir vos batailles, je suppose. L'optimisation pour SPU est une explosion et vous vous sentez terriblement intelligent une fois que vous avez obtenu cette solution serrée: o)
Kaj
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.