5MMCEAMC/SLE-3A Travaux Pratiques Utilisation des

publicité
5MMCEAMC/SLE-3A Travaux Pratiques
Utilisation des extensions SIMD du pentium pour le calcul
de la conversion YCbCr vers RGB
Frédéric Pétrot
Durée : 3 heures
1
Organisation
Le travail se fait en binôme cette fois-ci (l’expérience forme la jeunesse). Le sujet est découpé en
parties qui peuvent au départ être faites plus ou moins indépendamment, aussi il est raisonnable que
les 2 membres du binôme travaillent en parallèle (de degré 2 ici).
Merci de m’envoyer votre fichier conv.c au plus tard lundi soir minuit, c’est là dessus que vous
serez notés (ou pas, ...).
2
Introduction
Ce TP a pour objectif de mettre en pratique l’utilisation des opérations SIMD de base (dites MMX) du
processeur x86. SIMD signifie Single Instruction, Multiple Data, ce qui veut dire que la même opération
va être exécutée sur n données différentes (et indépendantes) simultanément. Un exemple simple est
l’exécution d’une addition sur les 4 octets d’un mots de 32 bits considérés indépendamment.
Les instructions MMX d’Intel travaillent sur des registres de 64 bits dont le contenu peut être
considéré comme soit 8 octets (de 8 bits, dits bytes), 4 entiers courts de 16 bits (appelés words par
Intel), 2 entiers de 32 bits (appelés doubles), ou 1 entier long de 64 bits (encore dit quad). Il y a 57
instructions MMX, mais le travail demandé ne requière l’utilisation que d’un petit sous-ensemble. Pour
information, je n’ai utilisé que les 15 suivantes dans ma correction :
emms
movd
movl
movq
packuswb
paddw
pmulhw pmullw
psrlq
psrlw
psubw
punpckhwd
punpcklbw punpcklwd pxor
Une rapide explication des instructions MMX se trouve sur le site http://tommesani.com/index.
php/component/content/article/2-simd/34-mmx-primer.html. Attention, sur ce cite l’ordre des
opérandes dans les instructions est celui de l’assembleur Intel, alors que par défaut GAS utilise l’ordre
opposé : l’opérande 1 et le résultat sont à droite.
Le TP utilise un décodeur vidéo Motion-JPEG (celui du projet C de 1ère année pour être précis),
dans lequel nous allons tenter d’optimiser une fonction particulière, celle qui assure la conversion des
composantes de luminance et chrominance en rouge, vert, bleue pour l’affichage dans un frame buffer.
3
3.1
Travail demandé
Préliminaires
Récupérez sur le site du cours (https://ensiwiki.ensimag.fr/images/d/d1/Tp2_src.tgz) l’archive contenant les sources nécessaires au TP. Cette archive s’expand dans le répertoire tp2_src/, et
contient une vidéo ice_age_256x144_444.mjpeg qui sera notre benchmark, un Makefile, des fichiers
objets contenant les différentes phases du décodage hormis la conversion YUV vers RGB, et trois fichiers
sources contenant diverses implantations (dont certaines partielles) de la conversion.
1
conv-float.c
la version initiale en virgule flottante ;
conv-int.c
la version en entiers ;
conv-v4si.c
la version avec le support vectoriel du compilateur gcc ;
conv-loop4.c
la version qui est le sujet de la première partie de la question 1 ;
conv-unrolled4.c
la version qui est le sujet de la deuxième partie de la question 1 ;
conv-mmx.c
la version qui est le sujet des questions 2 et suivantes.
L’exécutable crée prend 1, 2 ou 3 arguments. Le premier est le nom de la vidéo, le second le nombre
de frame à décoder, et le troisième n’importe quoi. En présence de ce troisième argument, le résultat du
décodage n’est pas affiché (à 25 images par secondes), ainsi c’est la vitesse brute qui peut être mesurée.
Afin de voir combien de temps prend le décodage (sur le film complet), on peut lancer :
petrot@tilleul% time ./mjpeg-float ice_age_256x144_444.mjpeg -1 no
./mjpeg ../src/ice_age_256x144_444.mjpeg -1 no 3,88s user 0,02s system 99% cpu 3,905 total
Question 1
Le MMX permettant d’exploiter le parallélisme des données, proposez une nouvelle version de cette
fonction, toujours écrite en C, qui mette en évidence un parallélisme de degré 4 sur la boucle interne. Il
n’est pas possible d’exprimer le parallélisme dans le C standard, aussi on utilisera des tableaux de taille
4. Dans un premier temps, on utilisera des boucles qui vont itérer de 0 à 3 sur ces tableaux. Relancez
l’exécution pour vérifier que vos modifications n’ont pas d’impact sur le fonctionnement.
Dans un second temps, on déroulera explicitement les boucles de 0 à 3, ce qui nécessite de la
recopie de code. Il est clair que ce n’est pas une pratique de programmation recommandée, mais on
fera une exception cette fois, c’est pour la bonne cause. Relancez l’exécution pour vérifier que le résultat
est toujours
Quelques informations supplémentaires
Comme nous travaillons avec un parallélisme de degré 4 et que les registres sont de 64 bits, la taille
des données manipulées sera de 64
4 = 16 bits.
Le code assembleur s’écrit inline grâce aux directives __asm__. Les registres MMX se notent %mm0 à
%mm7.
La syntaxe des cas d’utilisations qui vous seront nécessaires est :
— __asm__("movq
%mm5, %mm4"); pour les opérations impliquant deux registres MMX déterminés ;
— __asm__("movq
%0, %%mm4"::"r"(var)); pour mettre dans le registre %mm4 le contenu de
la variable var (considérée dans un registre, mettre "m" à la place de "r" si on veut une variable
en mémoire) ;
— __asm__("movq
%%mm2, %0":"=r"(var)); pour mettre dans la variable var (qui est dans
un registre) la valeur contenue dans %mm2. Si l’on veut écrire en mémoire, il faut alors mettre
"=m" à la place de "=r" ;
— une valeur absolue peut être utilisée dans certains cas, il faut alors utiliser la notation $1 pour la
constante 1. Si une constante doit être utilisée mais que l’instruction ne peut en prendre, alors il
faut passer par un movl.
2
Quelques exemples utiles :
— mise à zéro du registre mmx mm0 : __asm__("pxor %mm0, %mm0"); ;
— chargement d’une constante 64 bits dans un registre :
uint64_t poor = 0xdeadbeef8badf00d;
__asm__("movq %0, %%mm3" ::"r"(poor));
— décalage par une constante (ici 8) : __asm__("psrlw
$8, %mm3");.
Les registres MMX étant ceux du coprocesseur flottant, on peut inspecter leur valeur grâce à i fl
dans gdb.
Question 2
Chargez les 4 premiers octets du premier macro-bloc Y sous forme de mots de 16 bits dans le
registre %mm0. Astuce : utiliser l’instruction punpcklbw.
Vous pourrez utiliser les instructions suivantes pour vérifier que tout se passe bien :
#ifdef YCrCb_DEBUG
/* Ckecing the intermediate results */
uint16_t Y[4];
__asm__("movq %%mm0, %0":"=m"(Y[0]));
/* OK! */
#endif
Question 3
Chargez les 4 premiers octets des macro-blocs Cr et Cb sous forme de mots de 16 bits et leur
soustraire 128. Placer les résultats dans les registres %mm1 et %mm2.
Question 4
Effectuez le calcul de 4 composantes R dans les 4 octets de poids faible de %mm3 simultanément à
partir de %mm0, %mm1 et des constantes appropriées.
Faites de même pour B dans %mm4 puis G dans %mm5.
Astuce : utiliser l’instruction packuswb pour se ramener à des octets.
Question 5
Construisez 4 mots de 32 bits ayant chacun comme structure 0RGB à partir de %mm3, %mm4 et %mm5.
Astuce : l’opération s’apparente à une transposition de matrice 4x4, et peut se faire avec les instructions
punpcklbw, punpcklwd et punpckhwd.
3
Téléchargement