MMX ( 2 ) avec Delphi 6 / Kylix

Dans mon premier article, j'ai présenté le principe d'utilisation des instructions MMX et le résultat, en terme de performance, sur un programme test. Il est maintenant temps de travailler en conditions réelles. Pour ceci, j'ai repris mon programme de traitement d'images et j'ai essayé de reprogrammer les opérations en utilisant les instructions MMX. Un petit tour dans la documentation d'Intel s'impose.

Article lu   fois.

L'auteur

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

Voici donc le début de la page 374 de la documentation d'Intel. On trouve de haut en bas :

Image non disponible
  • le nom abrégé et le nom développé de l'instruction
  • un tableau avec le code binaire (Opcode), le format de l'instruction et une description sommaire. Déjà avec ceci, vous avez la majorité des infos sur l'instruction. Elle multiplie les words de mm (mm pour registre mmx) par ceux de mm/m64 (un registre mmx ou une adresse en mémoire), les additionne deux à deux et remet le tout dans mm (celui de départ).
  • une description détaillé si vous n'avez pas bien compris avec des détails supplémentaires
  • parfois, un graphe pour visualiser le principe de l'opération, et c'est bien pratique pour les instructions MMX.
  • ensuite, un tas de chose dont vous n'avez pas besoin en général et en particulier pour utiliser les instructions MMX

Ensuite l'utilisation de la fenêtre FPU qui peut afficher le contenu des registres MMX est d'un grand secours :

Image non disponible

Passons maintenant à la transformation d'une image couleur en niveau de gris. Le niveau de gris est calculé par combinaison de la luminosité des trois couleurs primaires (RGB pour Red, Green, Blue) :
L = R*0.715160 + G*0.212671 + B*0.072169
Néanmoins, un tel calcul faisant appel à des réels serait assez lourd aussi j'utilise une petite variante :
L = (R*183 + G*54 + B*18) / 256
qui ne travaille que sur des entiers avec une division très facile à mettre en œuvre.
J'emploie cette méthode aussi bien sans qu'avec les instructions MMX.

Etudions plus précisément l'emploi des instructions MMX. Les registres MMX (notés de mm0 à mm7) sont constitués de 64 bits qui peuvent être décomposés en 2 dword (appelé integer en pascal), en 4 word ou en 8 byte. J'ai retenu cette dernière représentation pour la figure suivante. Concernant les pixels, ils sont stockés dans des integer, qui se décomposent en 4 byte correspondant aux trois couleurs primaires et à la transparence.

La figure suivante présente l'ensemble des opérations que subit un pixel :

Image non disponible
  • Au départ, l'adresse du pixel est stockée dans EAX. Movd ramène le pixel dans mm2.
  • punpcklbw sert à entrelacer mm2 avec mm0 qui a été préalablement mis à zéro. Ceci revient à passer de byte en word, en prévision de l'instruction suivante.
  • On multiplie chaque couleur par son coefficient de luminosité avec pmaddwd et on commence à additionner les résultats.([a1;a2]=B*u+G*v et [b1;b2]=R*w)
  • psrld divise par 256 en décalant de 8 bits vers la droite
  • désentralacement avec packuswb pour tout ramener en partie basse du registre mmx.
  • la multiplication et addition ( pmaddwd ) par mm3 permet d'additionner les deux restants (c=a1+b1).
  • movd renvoie le résultat dans un registre classique.
  • la multiplication ( imul ) par ECX préalablement mis à $010101 permet d'étendre le byte de luminosité à l'ensemble du pixel.
  • enfin, mov renvoie le résultat dans le pixel d'origine.

Voici le code source (8 ko) correspondant. Il a été écrit sous D3 mais les instructions MMX ne fonctionnent que sous D6 ou Kylix.
J'ai effectué quelques tests de performance avec mon PC (Celeron 375 Mhz, cache L2 128 ko) sur une image de 1024x815 et j'en ai déduis le nombre moyen (écart type < 1%) de cycles d'horloge utilisés par pixel :

Opération Instructions MMX Nb d'instructions Nb cycles
avec D6
Gain MMX Nb cycles
avec Kylix
Gain MMX Gain
Kylix / D6
Gris Non 25 23,9   25,6   -7,2 %
  Oui 14 21,5 10,3 % 21,2 17,3 % +1,2 %
Négatif Non 4 14,0   14,9   -6,3 %
  Oui 3 10,2 27,4 % 14,8 0,7 % - 42,1 %
Flou Non 65 120,4   119,1   +1,0 %
  Oui 29 25,0 79,3 % 25,4 78,7 % -1,8 %

J'ai aussi rajouté le nombre d'instruction déduit de la fenêtre CPU.
Quelques commentaires sur les résultats :

Mise en niveaux de gris
sans MMX : exécution très efficace avec un nombre de cycle proche, voir en dessous du nombre d'instruction, certainement grâce à l'architecture superscalaire du microprocesseur qui permet parfois d'exécuter deux instructions en parallèle.
avec MMX : améliore de la vitesse mais pas autant que ne le laissait espérer le nombre d'instruction

Négatif
sans MMX : nous sommes largement au dessus de la théorie. Un autre facteur doit limiter la vitesse.
avec MMX : sous Windows, nous avons un gain proportionnel au nombre d'instruction, qui reste donc largement au dessus de la théorie. Sous Linux, il n'y a pas de gain. Le même facteur limitant que sans MMX semble exister.

Réalisation d'un flou
sans MMX : le nombre de cycle est le double de celui des instructions. Cette différence provient sans doute du fait que les variables locales ne peuvent être stockées dans les registres mais doivent être transférées en la mémoire, ce qui peut prendre du temps
avec MMX : d'abord, j'ai un peu triché car je n'ai pas trouvé de moyen aisé de diviser par neuf après avoir additionner le contenu de 3x3 pixels, alors j'ai divisé par huit, qui s'obtient par un simple décalage de 3 bits. Le résultat est par contre fulgurant, non seulement j'ai divisé le nombre d'instruction par deux, mais en plus les nombres de cycle et d'instruction sont équivalents, conduisant à une vitesse globale multipliée par 5. Une part de cette performance est certainement due à la gestions des variables locales, qui tiennent toutes dans les registres.

En conclusion, je trouve qu'à part pour le flou, le recours aux instructions MMX n'est pas justifié. Je pense qu'en dessous d'un certain nombre d'instructions par pixel, le transfert de l'image (3,3 Mo) entre la mémoire externe et le microprocesseur devient le facteur limitant. Ce seuil serait de 10 cycles sous Win32 et de 15 sous Linux, la différence pouvant provenir de gestions différentes de la mémoire cache du microprocesseur par l'OS.

Il convient donc de bien étudier une opération avant d'essayer de l'optimiser avec les instructions MMX. Parmi les critères favorables :

  • possibilité de ramener les variables locales dans les registres en utilisant les instructions MMX
  • calcul lourd (plutôt que rapide et limité par la mémoire)
  • possibilité de réduire significativement le nombre d'instruction avec le MMX

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.