Tp 8 : SIMD - MMX



L'objectif

Le but de ce tp est de vous montrer l’intérêt des instructions MMX du Pentium, et ainsi vous montrer ce qu’est une instruction SIMD.

Le temps imparti

1h30

Le sujet

Non, aprés ce Tp vous ne serez pas capable de reécrire le jeu DOOM en 3D sur un P4 :)
Mais vous comprendrez plus facilement l'utilité des instructions SIMD, surtout quand on a besoin d'appliquer les mêmes calculs aux milliers de points d'une image.

A faire

Saisir le programme suivant :

mmx.s
  1. .data
  2. .align 8
  3. Nombre1:
  4. .octa 0x1122334455667788
  5. Nombre2:
  6. .octa 0xFFEEDDCCBBAA9988
  7. Nombre3:
  8. .octa 0x0
  9. Nombre4:
  10. .octa 0x0
  11. Nombre5:
  12. .octa 0x0
  13. .text
  14. .global main
  15. main:
  16. pushl %ebp
  17. movl %esp,%ebp
  18. movq Nombre1,%mm0
  19. movq Nombre2,%mm1
  20. paddb %mm0,%mm1 #octet par octet
  21. movq %mm1,Nombre3
  22. movq Nombre2,%mm1 #mm1 a été modifié !
  23. paddw %mm0,%mm1 #16 bits par 16 bits
  24. movq %mm1,Nombre4
  25. movq Nombre2,%mm1 #mm1 a été modifié !
  26. paddd %mm0,%mm1 #32 bits par 32 bits
  27. movq %mm1,Nombre5
  28. leave
  29. ret

Compiler ce programme avec la commande :

$ gcc -gstabs mmx.s -o mmx

Le commutateur -g indique à gcc qu'il doit inclure des informations de débogage. stabs indique le format des données à inclure.
Déboguer ensuite votre programme avec l'une des commandes suivantes :

$ gdb ./mmx
$ ddd ./mmx

DDD utilise gdb, mais encapsulé dans une interface graphique plus agréable à utiliser. D'ailleurs, la console de gdb est visible dans le volet inférieur de la fenêtre...

Placer un point d'arrêt sur la ligne 28 : leave. Sous DDD, il suffit de double-cliquer dans la marge devant celle-ci... Si vous avez laissé une marge !
Sous GDB, tapez la commande :

(gdb) break 28

Lancer ensuite votre programme : Cliquer sur le bouton Run sous DDD, ou lancer la commande

(gdb) run

L'exécution s'arrête automatiquement à la ligne choisie. Il est aussi possible d'exécuter le programme pas-à-pas en utilisant les commandes step.

Il est maintenant temps de regarder le contenu de la mémoire. On va pour cela utiliser la fonction display suivie d'un format et d'une adresse en mémoire :

(gdb) display /9xg

DDD vous permet de faire de méme, ou de façon graphique en utilisant la commande graph display.
Dans cette commande, le format spécifié indique trois choses :

9
Affiche 9 éléments successifs.
x
Affiche en hexadécimal
g
Affiche des mots de 8 octets

L'affichage, sur deux colonnes, vous permet de comprendre pourquoi on a utilisé la directive .octa au lieu de .quad...
Cet affichage sera répété après chaque exécution d'une ligne de programme (step).

Pour annuler un affichage, vous pouvez utiliser la commande undisplay suivie du numéro de l'affichage à supprimer.

Faire les calculs à la main et comparer les résultats avec ceux de la machine. Ceci met en évidence la taille des opérations MMX employées.


GDB vous permet donc d'ausculter votre programme sous toutes ses coutures. Il dispose de bien d'autres commandes, comme par exemple :

print /format expression
Affiche (une seule fois, contrairement à display) la valeur du symbole indiqué.
Exemple : print /x (char[8])Nombre1
info registers
Affiche les registres entiers usuels
info float
Affiche l'état de la FPU
info all-registers
Affiche TOUS les registres accessibles du processeur

GDB peut accepter différents types d'expressions :

Nombre2
La variable indiquée
&Nombre3
L'adresse de la variable indiquée
*Nombre3
La valeur pointée par la variable indiquée
$eax
Le contenu du registre spécifié
$mm0.v4_int16
Le contenu du registre spécifié comme un tableau de 4 entiers courts (short).
Reservé aux registres ayant des valeurs multiples (MMX/SSE).
1+3*4
Le résultat de l'opération spécifiée
...

Une extension de GCC vous permet d'utiliser la programmation vectorielle directement en C. Bien que ce ne soit pas du C standard, le support de GCC pour cette dernière est assez solide. Essayez le programme suivant :

  1. #include <stdio.h>
  2. typedef int v4si __attribute__ ((vector_size (16)));
  3. void print (char *msg, v4si vec_) {
  4. int *vec = (int*) &vec_;
  5. printf("%s = [%d, %d, %d, %d]\n",msg,vec[0],vec[1],vec[2],vec[3]);
  6. }
  7. int main() {
  8. v4si a = {1,2,3,4};
  9. v4si b = {3,2,1,4};
  10. v4si c;
  11. print("a",a);
  12. print("b",b);
  13. c = a + b;
  14. print("c1",c);
  15. c = a - b;
  16. print("c2",c);
  17. return 0;
  18. }

Bien entendu, si vous êtes sur une architecture 32 bits, vous devez activer le jeu d'instructions SSE pour en bénéficier :

gcc -march=pentium4 prog.c

Sur un processeur Core i7, ce programme (plus une boucle faisant 2 000 000 000 fois le calcul avant affichage) passe de 10 secondes à 4 secondes avec le SSE...


Pour les étudiants en avance, ou désireux d'aller plus loin

Je vous recommande d'expérimenter avec le SIMD. Par exemple, vous pouvez :


Retour à la liste des TP