Avec chaque nouvelle génération de processeurs Intel, de nouvelles instructions vectorielles de plus en plus complexes émergent. Bien que la longueur du vecteur (512 bits) n'augmente pas dans un avenir proche, de nouveaux types de données et types d'instructions apparaîtront. Par exemple, qui peut comprendre en un coup d'œil ce que fait une telle intrinsèque (et l'instruction de processeur correspondante)?
Logique ternaire au niveau du bit qui permet d'implémenter n'importe quelle fonction binaire à trois opérandes; la fonction binaire spécifique est spécifiée par valeur dans imm8.
__m512i _mm512_mask_ternarylogic_epi32 (__m512i src, __mmask8 k, __m512i a, __m512i b, int imm8) FOR j := 0 to 15 i := j*32 IF k[j] FOR h := 0 to 31 index[2:0] := (src[i+h] << 2) OR (a[i+h] << 1) OR b[i+h] dst[i+h] := imm8[index[2:0]] ENDFOR ELSE dst[i+31:i] := src[i+31:i] FI ENDFOR dst[MAX:512] := 0
OK, disons que nous avons compris comment cela fonctionne. Le niveau de complexité suivant est le débogage du code qui utilise intensivement ces intrinsèques.
Ceux qui utilisent régulièrement intrinsèques connaissent un site très utile -
Intel intrinsics guide . Si vous regardez attentivement comment cela fonctionne, il est facile de remarquer que le frontal javascript télécharge le fichier data-3.xxxml, qui décrit en détail tous les éléments intrinsèques, avec un code similaire à Matlab. (Par exemple, celui que j'ai copié dans le titre du message.)
Mais lorsque nous utilisons des intrinsèques pour accélérer le code, nous n'écrivons pas dans Matlab, mais en C et C ++! Il y a trois mois, un client m'a demandé s'il y avait une implémentation des intrinsèques vectorielles dans C pour le débogage, et j'ai décidé d'écrire un analyseur qui traduit le code du Guide Intrinsics en C. ou ajoutez debug printf).
Par exemple, une opération à partir d'un titre de poste se transforme en
for (int j = 0; j <= 15; j++) { if (k & (1 << j)) { for (int h = 0; h <= 31; h++) { int index = ((((src_vec[j] & (1 << h)) >> h) << 2) | (((a_vec[j] & (1 << h)) >> h) << 1) | ((b_vec[j] & (1 << h)) >> h)) & 0x7; dst_vec[j] = (dst_vec[j] & ~(1 << h)) | ((((imm8 & (1 << index)) >> index)) << h); } } else { dst_vec[j] = src_vec[j]; } }
C'est vrai, c'est beaucoup plus compréhensible? Pas vraiment? Eh bien, je viens de choisir une fonction complexe comme exemple. Habituellement, lorsque vous déboguez du code avec des éléments intrinsèques (par exemple, DSP), vous devez garder à l'esprit à la fois l'algorithme et les fonctionnalités de chaque instruction. Étant donné que les instructions fonctionnent avec de longs vecteurs et que les algorithmes DSP sont souvent basés sur des mathématiques sérieuses, ma tête ne s'en sort pas - il n'y a pas assez de mémoire à court terme et de concentration. Je soupçonne que je ne suis pas seul - plusieurs fois, j'ai même pensé que j'avais trouvé un bug dans les instructions. Ensuite, bien sûr, chaque fois qu'il s'est avéré que j'avais tort, et cela n'a pas fonctionné pour ouvrir un nouveau bogue FDIV. Mais si je pouvais, dans ces cas, déboguer étape par étape à l'intérieur des instructions, je comprendrais immédiatement dans quelles conditions une valeur apparaît dans le composant de mon vecteur à laquelle je ne m'attendais pas.
Les clients m'ont dit qu'ils utilisent cette bibliothèque pour déboguer des fonctions individuelles avec les intrinsèques AVX-512 sur un ordinateur portable qui ne prend en charge que AVX2. Bien sûr,
Intel SDE est beaucoup mieux adapté à cela - car il imite extrêmement précisément tous les jeux d'instructions. J'ai un ensemble de tests unitaires (également générés automatiquement) qui pour chaque intrinsèque de la bibliothèque comparent le résultat de son travail avec le résultat de l'exécution de l'instruction d'assembleur correspondante. Comme il sied aux tests unitaires, la plupart fonctionnent comme prévu. Mais certains intrinsèques de débogage à virgule flottante (double et simple précision) ne fonctionnent pas toujours correctement à 100%. Je dirais que parfois c'est un peu
-fast-math . Et il existe différents mécanismes d'arrondi! IEE754 a beaucoup de subtilités ...
Il existe une autre caractéristique importante de l'utilisation du débogage immintrin au lieu de SDE (que je n'approuve en aucune façon, mais je ne peux pas l'arrêter). Si vous compilez gcc ou clang avec l'option, par exemple,
-march = nehalem , alors gcc et clang renvoient des vecteurs 512 bits à partir des fonctions de la pile à partir des fonctions, et ICC les renvoie toujours à ZMM0. Le compilateur Intel ne peut donc pas être utilisé dans ce mode. Et gcc a une option utile
-Og , qui aide au débogage, y compris avec le débogage immintrin.
Il existe plusieurs intrinsèques dont l'action principale est de modifier le contenu du registre, par exemple, ou les drapeaux. Je n'ai pas mis en œuvre de telles instructions. Eh bien, alors que mon analyseur n'est pas tout à fait prêt, la mise en œuvre d'environ 10% des intrinsèques n'est pas encore disponible.
L'utilisation du débogage immintrin est très simple - vous n'avez pas besoin de changer le code source, mais vous devez ajouter une compilation conditionnelle pour inclure immintrin_dbg.h au lieu d'immintrin.h en cas de construction de débogage.
Vous pouvez le télécharger
sur github .