Accueil
Rechercher:
sur developpez.com sur les forums
Forums | Tutoriels | F.A.Q's | Participez | Hébergement | Contacts
Club Emploi Blogs   TV   Dév. Web PHP XML Python Autres 2D-3D-Jeux Sécurité Windows Linux PC Mac
Accueil Conception Java DotNET Visual Basic  C  C++ Delphi MS-Office SQL & SGBD Oracle  4D  Business Intelligence
FORUMS DELPHI F.A.Q DELPHI TUTORIELS DELPHI LIVRES COMPOSANTS SOURCES DEFI TELECHARGEZ DELPHI TV

Instructions SIMD sur les réels

Date de publication : 05/11/2002

Date de mise a jour : 05/11/2002

Par Eric SIBERT (Site)
 


I. Introduction
II. 3DNow!
III. SSE
IV. SSE2
V. Performances
VI. Kylix
VII. Conclusion


I. Introduction


SIMD est l'acronyme de Single Instruction Multiple Datas (une seule instruction, plusieurs données). Ceci recouvre en fait plusieurs jeux d'instructions qui ont été ajoutés aux microprocesseurs Intel et compatibles ces dernières années. Les instructions MMX ont été les premières de la série en permettant d'effectuer simultanément la même instruction sur plusieurs nombres entiers. Elles ont déjà été évoquées dans deux articles, ici et . Nous allons maintenant étudier les instructions SIMD concernant les nombres réels. Il s'agit des instructions SSE et SSE2 chez Intel ainsi que 3DNow! chez AMD. Ces instructions sont prises en charges par l'assembleur intégré (basm) de Delphi 6 et de Kylix. Donc, il faut abandonner temporairement le Pascal pour l'assembleur afin de les utiliser.

Nous aurons recours à un exemple simple, le calcul du milieu de deux points dans l'espace. Chaque point dispose de trois coordonnées x, y et z et nous devons juste faire la moyenne des coordonnées une à une pour trouver le milieu :

Ce qui se traduit en Pascal par:

type TZPoint = record X, Y, Z : Single; end; function MidPoint(const P1, P2: TZPoint): TZPoint; const Demi : single = 0.5; begin Result.X:= (P2.X+P1.X)*Demi; Result.Y:= (P2.Y+P1.Y)*Demi; Result.Z:= (P2.Z+P1.Z)*Demi; end;
Nous avons ici le code le plus optimisé possible en Pascal, en particulier la multiplication par 0,5 plutôt que la division par 2 permet de gagner beaucoup de temps. Le recours à une constante permet aussi d'améliorer le code généré. L'utilisation de l'assembleur classique, en supprimant les FWAIT à la fin des lignes de calcul n'apporte pas de gain important. Que reste-t-il pour aller plus vite? Les instructions SIMD! Nous allons commencer par le 3DNow!.


II. 3DNow!


Ces instructions sont disponibles sur les microprocesseurs AMD depuis le K6-2. Il faut aller sur le site web d'AMD pour trouver la description des instructions. Les instructions 3DNow! utilisent les registres MMX. Il s'agit de registres 64 bits qui peuvent ainsi contenir chacun deux nombres réels simple précision. Et les instructions 3DNow! permettent ensuite de réaliser simultanément deux fois la même opération. En pratique, nous devons transférer nos données vers les registres MMX, effectuer les opérations, récupérer les données et désactiver les registres MMX. Comme je ne dispose de machine correspondante, Matthijs Laan a fait le travail de codage pour moi :

function MidPoint_3DNow(const P1, P2: TZPoint): TZPoint; const PackedHalf: packed array[0..1] of Single = (0.5, 0.5); asm movq mm0, [PackedHalf] // chargement de la constante précédente movq mm1, [eax] // chargement de P1.X et P1.Y pfadd mm1, [edx] // addition de P2.X et P2.Y movd mm3, [eax + 8] // chargement de P1.Z pfadd mm3, [edx + 8] // addition de P2.Z pfmul mm1, mm0 // multiplication résultat sur X et Y par 0.5 pfmul mm3, mm0 // multiplication résultat sur Z par 0.5 movq [ecx], mm1 // déchargement de X et Y movd [ecx + 8], mm3 // déchargement de Z emms // désactivation de l'unité MMX end;
Le codage est aisé grâce à Delphi 6 qui reconnaît les instructions 3DNow!. Ça fonctionne et nous étudierons les performances à la fin de cet article en commun avec les autres jeux d'instructions.


III. SSE


Les instructions SSE sont apparues chez Intel avec le Pentium III puis ont été étendues au Celeron à partir du 533A. AMD les a portées sur ces microprocesseurs à partir de l'Athlon XP. Avec ces instructions, 8 nouveaux registres 128 bits ont été ajoutés au microprocesseur. Ils sont nommés de xmm0 à xmm7 et peuvent contenir 4 nombres réels simple précision. Nous pouvons ensuite réaliser quatre opérations simultanément. Néanmoins, Intel nous a réservé une mauvaise surprise en ne mettant, sur les PIII, qu'un bus 64 bits pour accéder aux registres xmm. Il faut donc deux instructions pour charger ou décharger un registre xmm :

movlps xmm0,[eax] //chargement de la partie basse de xmm0 avec P1 movhps xmm0,[eax+8] //chargement de la partie haute de xmm0 avec P1
Ensuite, certaines instructions comme les opérations (addition, multiplication ...) mettant en jeu un registre et une adresse mémoire exigent que cette adresse mémoire soit alignée sur 16 octets (=128 bits). Or le compilateur de Delphi aligne les données sur 4 octets (=32 bits) (Ref : Guide du langage Pascal Objet, Gestion de la mémoire). Première solution, ne pas utiliser les instructions à problème et trouver des méthodes de contournement. En terme de performance, c'est mauvais et mieux vaut ne pas utiliser les instructions SSE. Deuxième méthode, aligner les données sur 16 octets en remplaçant le gestionnaire de mémoire par défaut par le nôtre que nous avons pris soin de programmer dans cette optique. Pour ceux qui ne sont pas très familiers avec les gestionnaires de mémoire :-), Robert Lee a déjà fait le travail pour vous sous Delphi. Prenez son gestionnaire de mémoire et utilisez le dans votre projet en l'appelant en premier :

program Project1; uses MultiMM, // ici, le nouveau gestionnaire de mémoire Forms, Unit1 in 'Unit1.pas' {Form1};
Mais ce n'est pas suffisant car le nouveau gestionnaire de mémoire n'aligne que les variables que vous allouez dynamiquement. Et pour allouer une variable, rien de tel que new :

T4DPoint = packed record // définition d'un format de 16 octets X, Y, Z, T : Single; end; const CPackedHalf: T4DPoint = (x:0.5; y:0.5; z:0.5; t:0.5); // constante de multiplication var Serie1bis, Serie2bis, Rapidebis, PackedHalf : ^T4DPoint; begin [...] new(Serie1bis); // allocation dynamique des variables new(Serie2bis); new(Rapidebis); new(PackedHalf); // allocation dynamique d'une variable pour la constante PackedHalf^:=CPackedHalf; // transfert de la constante vers une variable alignée [...] MidPoint_SSE(Serie1bis^,Serie2bis^,Rapidebis^); // appel de la procédure de calcul [...] dispose(Serie1bis); // désallocation dynamique des variables dispose(Serie2bis); dispose(Rapidebis); dispose(PackedHalf); end;
Nous pouvons enfin passer au calcul proprement dit :

procedure MidPoint_SSE(const P1, P2 : T4DPoint; var result : T4DPoint); asm movlps xmm0,[eax] // chargement de la partie basse de xmm0 avec P1 movhps xmm0,[eax+8] // chargement de la partie haute de xmm0 avec P1 mov eax,[PackedHalf] // récupération de l'adresse de la constante addps xmm0,[edx] // addition de P2 à xmm0 (P1) mulps xmm0,[eax] // multiplication du résultat par 0.5 movlps [ecx],xmm0 // déchargement de la partie basse de xmm0 avec P1 movhps [ecx+8],xmm0 // déchargement de la partie haute de xmm0 avec P1 end;
Le calcul est simple, fonctionne et permet des gains de temps significatifs.


IV. SSE2


Les instructions SSE2 sont apparues avec le Pentium 4. Elles devraient être portées par AMD sur les Hammer (génération K8) à la mi-2003. Mais mon exemple fonctionne déjà sur les Athlon XP. Une partie des instruction SSE2 semble donc supportée par ce dernier. Outre l'introduction de nouvelles possibilités de calcul, elles correspondent à l'arrivée d'un bus 128 bits pour les registres xmm. Il devient donc possible de charger un registre xmm à l'aide d'une seule instruction, ce qui simplifie le code SSE sans changement par ailleurs :

procedure MidPoint_SSE2(const P1, P2 : T4DPoint; var result : T4DPoint); asm movaps xmm0,[eax] mov eax,[PackedHalf] addps xmm0,[edx] mulps xmm0,[eax] movaps [ecx],xmm0 end;
Lorsque vous avez déjà écrit votre code SSE, le passage au SSE2 est très facile et consiste à simplifier le code. Il ne faut donc pas s'en priver.


V. Performances


Venons en aux performances. J'ai mis dans le tableau suivant le temps moyen en nombre de cycle d'horloge pour calculer le milieu de deux points :

Architecture Proc / fréquence Pascal asm 1 (perso) asm 2 (Barry Kelly et al .) 3DNow ! (Matthijs Laan et al.) SSE (perso) SSE2 (perso) Puissance effective / MFlops
Intel P5 Pentium 90 38.5 35.5 32.6 X X X 17
  P 200 MMX 51.2 51.2 63.3 X X X 23
                 
Intel P6 Celeron 333@375 18.0 16.8 16.7 X X X 138
  P III 500@560 18.1 17.0 17.1 X 12.4 X 270
  P III 600 18.1 16.8 17.2 X 12.3 X 290
  P III 633 18.2 17.0 17.0 X 12.3 X 310
  P III 733 18.6 17.2 17.0 X 12.1 X 360
  P III 1 GHz 18.6 17.0 17.0 X 12.1 X 500
                 
Intel P7 P4 1.5 19.8 16.0 18.2 X 20.1 12.1 740
                 
AMD K6 K6-2 333 52.3 45.6 60.7 22.3 X X 90
                 
AMD K7 Duron 800 12.0 12.5 15.0 12.1 13.0 13.0 10.4 10.1 X X 460 480
  Athlon 700 13.3 12.0 12.0 9.3 X X 450
  Athlon 1.0 14.8 12.0 15.0 10.0 X X 600
  Athlon 1.1 12.0 12.0 13.0 10.0 X X 660
  Athlon 1.35 12.1 12.0 13.0 10.0 X X 810
  Athlon XP 1600 + 13.0 12.0 15.0 10.0 10.1 10.1 840
Pour les microprocesseurs de la famille Intel P6 avec instructions SSE, le gain est de 30 % en temps d'exécution, ce qui peut justifier le recours aux instructions SSE. Avec un Pentium 4, les instructions SSE sont pénalisantes et seul le recours au SSE2 permet un gain de 40%. Vu comme ça, le SSE2 ressemble à une arnaque qui rend obsolète le code SSE écrit pour les PIII et vous oblige à passer au SSE2 pour rattraper les anciennes performances.

Un tour chez AMD montre d'abord que le 3DNow! est très performant avec les K6 (gain 57 %). A l'opposé, avec la nouvelle architecture (Athlon et Duron), les gains sont faibles et l'opportunité d'écrire un code spécial 3DNow! se pose.

Enfin, la comparaison entre Intel et AMD est toute à l'avantage de ce dernier avec un code en Pascal aussi performant que celui optimisé SSE/SSE2 chez Intel. Ce constat est d'ailleurs confirmé par pas mal de tests sur Internet. Il ne faut toutefois pas perdre de vue que nous avons travaillé ici sur un exemple un peu fictif. Celui ci utilise peu les registres. Or, les instructions SSE disposent de 8 registres 128 bits contre 8 registres 64 bits pour 3DNow!. Avec le SSE, on peut alors envisager de stocker une matrice 4x4, très utile en 3D, et conserver l'autre moitié des registres disponibles pour faire passer les vecteurs à multiplier par la matrice. Ceci est impossible avec le 3DNow! et imposera de nombreux transferts en mémoire. De plus, le 3DNow! bloque l'unité FPU classique contrairement au SSE. Peut-être qu'un jour j'écrirai un article sur l'utilisation en conditions réelles des instructions SIMD :)))


VI. Kylix


Si l'assembleur intégré de Kylix supporte lui aussi les nouveaux jeux d'instructions SIMD, il n'existe par contre pas de gestionnaire de mémoire adapté aux instructions SSE et SSE2. Seules les instructions 3DNow! pourront être utilisées sous Linux.


VII. Conclusion


Nous avons vu dans cet article comment utiliser les instructions 3DNow!, SSE et SSE2. Leur mise en œuvre est simplifiée par l'assembleur de Delphi 6 qui reconnaît les mnémotechniques correspondants. Les gains en performances sont surtout intéressant pour le SSE et le SSE2.

Code source (13 ko, y compris le gestionnaire de mémoire)


Liste de mes articles :
Types énumérés, intervalle et ensemble
MMX avec Delphi 6 / Kylix
Préchargement de données dans le cache
MMX ( 2 ) avec Delphi 6 / Kylix
Instructions SIMD sur les réels
Internationaliser un projet Delphi
Installer D6 sous Windows 95
Tramage d'une image
Coder le PNG soi-même
Utiliser LibTiff avec Delphi
Ecrire une UDF FireBird avec Kylix


Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
Responsables bénévoles de la rubrique Delphi : Nono40 et Pedro - Contacter par EMail :
Vos questions techniques : forum d'entraide Delphi - Publiez vos articles, tutoriels et cours
et rejoignez-nous dans l'équipe de rédaction du club d'entraide des développeurs francophones
Nous contacter - Copyright © 2000-2008 www.developpez.com - Legal informations.