Je vais améliorer les performances du noyau OCL et vouloir clarifier le fonctionnement des transactions de mémoire et le meilleur accès à la mémoire (et pourquoi). Le noyau est alimenté par des vecteurs de 8 entiers définis comme array: int v [8], ce qui signifie que le vecteur entier doit être chargé dans les GPR avant tout calcul. Donc, je crois que le goulot d'étranglement de ce code est la charge de données initiale.OpenCL (AMD GCN) Motif global d'accès à la mémoire pour les données vectorisées: stride ou contigu
D'abord, je considère quelques bases théoriques.
Target HW est un Radeon RX 480/580 doté d'un bus mémoire GDDR5 de 256 bits, sur lequel la transaction de lecture/écriture en rafale a une granularité de 8 mots, d'où une transaction de mémoire de 2048 bits ou 256 octets. , Je crois, ce que CL_DEVICE_MEM_BASE_ADDR_ALIGN fait référence à:
Alignment (bits) of base address: 2048.
Ainsi, ma première question: quel est le sens physique du cacheline 128 octets? Conserve-t-il la partie de données récupérée par simple lecture en rafale mais pas vraiment demandée? Que se passe-t-il avec le reste si nous avons demandé, disons, 32 ou 64 octets - ainsi, les restes dépassent la taille de la ligne de cache? (Je suppose, il sera juste jeté - alors, quelle partie: tête, queue ...?)
Maintenant, revenons à mon noyau, je pense que le cache ne joue pas un rôle important dans mon cas, car une rafale lit 64 entiers -> une transaction de mémoire peut théoriquement alimenter 8 éléments de travail à la fois, il n'y a pas de données supplémentaires à lire et la mémoire est toujours fusionnée.
Mais encore, je peux placer mes données avec deux modèles d'accès différents:
1) contiguë
a[i] = v[get_global_id(0) * get_global_size(0) + i];
(Wich en fait perfomed comme)
*(int8*)a = *(int8*)v;
2) intercalés
a[i] = v[get_global_id(0) + i * get_global_size(0)];
Je pense que dans mon cas contigu serait plus rapide car comme mentionné ci-dessus une transaction de mémoire peut complètement bourrer 8 éléments de travail avec des données. Cependant, je ne sais pas, comment le planificateur dans l'unité de calcul fonctionne physiquement: a-t-il besoin de toutes les données pour être prêt pour toutes les voies SIMD ou seulement la première partie pour 4 éléments SIMD parallèles serait suffisante? Néanmoins, je suppose qu'il est assez intelligent pour fournir entièrement des données au moins une CU en premier, dès que les UC peuvent exécuter des flux de commande de manière indépendante. Dans le second cas, nous devons effectuer 8 * global_size/64 transactions pour obtenir un vecteur complet. Donc, ma deuxième question: est-ce que ma supposition est exacte?
Maintenant, la pratique.
En fait, je diviser la tâche entière en deux noyaux parce qu'une partie a moins de pression de registre qu'une autre et peut donc employer plus d'éléments de travail. Donc d'abord je jouais avec pattern comment les données stockées en transition entre les noyaux (en utilisant vload8/vstore8 ou cast to int8 donnaient le même résultat) et le résultat était quelque peu étrange: le noyau qui lit les données de manière contiguë fonctionne environ 10% plus vite CodeXL et par la mesure du temps de l'OS), mais le noyau qui stocke les données de manière contiguë fonctionne étonnamment plus lentement. Le temps global pour deux noyaux est alors à peu près le même. Dans mes pensées, les deux doivent se comporter au moins de la même manière - soit être plus lent ou plus rapide, mais ces résultats inverses semblaient inexplicables.
Et ma troisième question est: quelqu'un peut-il expliquer un tel résultat? Ou peut-être que je fais quelque chose de mal? (Ou complètement faux?)
Merci pour la réponse. Cependant, je ne parle pas d'accès coalescé ou entrelacé. Peut-être que mes écrits ne sont pas si clairs, mais l'accès est toujours coalescé - la différence est seulement de lire le vecteur de données par rapport à l'élément-sage. J'ai corrigé la question afin de clarifier un peu. – qpdb
@qpdb la chose que vous appelez contiguous est contiguë du point de vue du noyau et est entrelacée du point de vue de la mémoire à un cycle donné, de sorte que la lecture du premier élément de chaque workitem peut mettre en cache les données restantes. Mais en écrivant, il n'y a pas ce comportement donc ça devient plus lent.La chose que vous appelez "entrelacé" est réellement contiguë à un cycle donné pour la mémoire car le programmateur en lecture/écriture (ou n'importe quelle partie combinant lectures/écritures) peut servir n éléments de travail qui lisent/écrivent uniformément sur une grande série d'éléments voisins. –
BTW, merci encore pour pintant à la documentation. J'ai appris à partir de là: "Les appareils de Southern Island ne supportent pas les écritures coalescées, cependant, les adresses continues au sein des groupes de travail fournissent des performances maximales.". Cette information semble double-étrange, puisque mes expériences donnent un résultat absolument différent. Ou est-ce que je comprends tout le concept de «coalescence» entièrement au contraire? – qpdb