• Aucun résultat trouvé

Notes sur l’implémentation, résultats expérimentaux

Il y a, en effet, autant de façons d’implémenter les algorithmes décrits dans ce chapitre qu’il y a d’architectures, de programmeurs pour l’écriture et de compilateurs pour la compilation du code.

Les implémentations sur les architectures parallèles peuvent être facilement déduites de nos des- criptions formelles des algorithmes présentés dans ce chapitre. Le parallélisme le plus simple, utilisable dans ces cas, est celui de la replication fonctionnelle représentée par le skeleton algorithmique farm, cf. 4.4.2.1, page 67. Pour l’employer, nous nous intéressons à toutes les parties de notre algorithme qui utilisent la fonction map de l’application d’une fonction sur tous les éléments d’un stream. Toutes ces parties peuvent être récrites en utilisant le skeleton algorithmique farm à la place de la fonction map. Ainsi, nous changeons complètement la manière de travailler d’une telle partie de notre algorithme et nous passons de l’exécution en séquence, exprimée par map, à l’exécution en parallèle, exprimée par farm. Le choix exact dépend de nos exigences et de nos possibilités matérielles lors de l’implémentation. De plus, ces algorithmes entrent dans la logique du paradigme Divide and Conquer, présenté par le skeleton algorithmique dc, cf. 4.4.2.2, page 67. La division d’un problème global à des problèmes plus petits et locaux est propre aux algorithmes de ce chapitre travaillant sur les macro blocs. Il serait également envisageable d’exprimer ces algorithmes en termes du Divide and conquer et en utilisant le skeleton algorithmique dc car la manière de travailler de ce skeleton est identique à ce que nous faisons par le découpage d’un array sur les macro blocs, l’application de la fonction locale et son recollage effectué à la fin.

Concernant l’implémentation SIMD, la première chose que nous devrions souligner est la demande d’alignement des données de l’image dans la mémoire aux bornes qui sont les multiples de la taille N du registre multimédia. Si l’image a des dimensions qui sont des multiples de N et si, de plus, elles est alignée aux blocs de mémoire par N, notre implémentation se révèle simple. Dans le cas contraire, nous devrions faire face aux effets particuliers du travail avec les données non-alignées. L’accès aux données non-alignées est possible sur les architectures multimédia via les instructions spécialisées pour un accès non-aligné mais le coût d’un tel accès est, en général, supérieur à un accès alignée. C’est du au fait que pour la lecture d’une donnée non-alignée vers un registre, l’architecture utilise deux lectures consécutives des zones alignées couvrant les données voulues suivies par leur extraction vers le registre. Ces instructions peuvent avoir un coût relativement faible, mesuré dans les cycles d’horloge, comme c’est le cas pour les instructions Intel SSE3. La figure 6.7 illustre un exemple de la transposition d’une image alignée mais dont les dimensions ne sont pas un multiple de la taille du registre multimédia.

Nous présentons également deux exemples du code en langage C implémentant la transposition d’un macro bloc par la diagonale.

Le premier, présenté sur la fig. 6.6, est un code qui provient du MorphoMedia, un outil logiciel que nous avons développé dans le cadre de cette thèse. Il s’agit d’un code programmé comme les direc- tives du préprocesseur (cf. #define) qui utilise les fonctions commençant par mrph_asm_ qui nous

64-bit (8x8-bit) snd Éléments infortmatifs de l’image A fs t B C D E F G H I ATD DTD BTD ETD GTD CTD FTD HTD ITD

FIG. 6.7 : La transposition d’un array dont les dimensions ne sont pas un multiple de la taille d’un registre

multimédia de 64 bits ; TD = macro bloc transposé par la diagonale

servent comme les invariables architecturales dans notre code. Ainsi, le même code peut être réutilisé sur plusieurs architectures multimédia.

#define MRPH_MACRO_TrByMainDiagonal_8x8_t8(\

A0, A1, A2, A3, A4, A5, A6, A7, \ B0, B1, B2, B3, B4, B5, B6, B7 \ ) \ {\ B0 = mrph_asm_mshflo_iu8vec8(A0, A4);\ B1 = mrph_asm_mshflo_iu8vec8(A1, A5);\ B2 = mrph_asm_mshflo_iu8vec8(A2, A6);\ B3 = mrph_asm_mshflo_iu8vec8(A3, A7);\ B4 = mrph_asm_mshfhi_iu8vec8(A0, A4);\ B5 = mrph_asm_mshfhi_iu8vec8(A1, A5);\ B6 = mrph_asm_mshfhi_iu8vec8(A2, A6);\ B7 = mrph_asm_mshfhi_iu8vec8(A3, A7);\ \ A0 = mrph_asm_mshflo_iu8vec8(B0, B2);\ A1 = mrph_asm_mshflo_iu8vec8(B1, B3);\ A2 = mrph_asm_mshfhi_iu8vec8(B0, B2);\ A3 = mrph_asm_mshfhi_iu8vec8(B1, B3);\ A4 = mrph_asm_mshflo_iu8vec8(B4, B6);\ A5 = mrph_asm_mshflo_iu8vec8(B5, B7);\ A6 = mrph_asm_mshfhi_iu8vec8(B4, B6);\ A7 = mrph_asm_mshfhi_iu8vec8(B5, B7);\ \ B0 = mrph_asm_mshflo_iu8vec8(A0, A1);\ B1 = mrph_asm_mshfhi_iu8vec8(A0, A1);\ B2 = mrph_asm_mshflo_iu8vec8(A2, A3);\ B3 = mrph_asm_mshfhi_iu8vec8(A2, A3);\ B4 = mrph_asm_mshflo_iu8vec8(A4, A5);\ B5 = mrph_asm_mshfhi_iu8vec8(A4, A5);\ B6 = mrph_asm_mshflo_iu8vec8(A6, A7);\ B7 = mrph_asm_mshfhi_iu8vec8(A6, A7);\ }

FIG. 6.6 : Code de la transposition par diago-

nale d’un macro bloc 8 × 8 en langage C uti- lisant l’outil de développement multiplateforme MorphoMedia

Le deuxième exemple est présenté sur la fig. 6.8. Il s’agit d’un code écrit manuellement qui assure la même fonctionnalité de transposition d’un macro bloc par la diagonale mais qui utilise les fonctions intrinsèques du compilateur pour les processeurs compatibles Intel MMX/SSE2.

La table 6.1 présente les résultats expérimentaux pour la transposition par la diagonale et par l’antidia- gonale d’une image 512 × 512 dont les éléments sont du type unsigned integer de 8 bits sur le processeur In- tel Pentium 4 de 2.4 GHz par l’exécution en un seul thread. La zone de mémoire où sont stockées les don- nées est distincte à l’entrée et à la sortie. Nous consta- tons un gain de temps déjà entre l’implémentation gé- nérique qui consiste en l’utilisation des fonctions d’ac- cès au pixel et une implémentation qui utilise le tra- vail avec les pointeurs. Mais le gain que nous obtenons lors de l’utilisation des instructions MMX est plus inté- ressant, surtout si nous comptons utiliser la transposi- tion comme une des opérations de base dans nos algo- rithmes de morphologie mathématique.

Ce qui peut être assez surprenant c’est la durée de l’implémentation générique et même celle via poin- ter++pour un tel algorithme de base sur une machine relativement puissante de nos jours et cadencée à 2.4 GHz. Ainsi, nous accueillons avec plaisir la possibilité d’obtenir, sans aucun investissement dans le matériel existant, un algorithme plus rapide.

La figure 6.9 nous montre les représentations graphiques des tests de performance que nous avons effectué pour l’algorithme de la transposition par diagonale et plusieurs tailles d’images. À l’échelle logarithmique, nous verrons bien que la différence entre les implémentations non-SIMD (générique et via pointer++) où nous avons laissé toutes les optimisations au compilateur, et celles qui implémentent notre algorithme SIMD est importante pour toutes les tailles d’images. Avec grands taux d’accélérations s’élevant jusqu’à 33.8 pour les images de 1024 × 1024 de 8 bits si on compare l’implémentation SIMD utilisant la technologie Intel SSE2 et l’implémentation classique via pointer++ (cf. tab. 6.1).

Image

Transposition par

Méthode diagonale antidiagonale

d’implémentation Temps Taux Temps Taux ms d’accélération ms d’accélération 5122× 8 bits

générique élément par élément 2.61 0.58 3.02 0.50

via pointer++ 1.51 1.00 1.51 1.00

instructions MMX 0.30 5.03 0.31 4.87

instructions SSE2 0.23 6.57 — —

10242

× 8 bits

générique élément par élément 61.3 0.99 61.9 0.99

via pointer++ 60.9 1.00 61.7 1.00

instructions MMX 2.2 27.7 2.2 28.0

instructions SSE2 1.8 33.8 — —

Implémentation sur Intel Pentium 4 @ 2.4 GHz (single thread, 8 ko L1, 512 ko L2). La zone de mémoire de sortie est distincte de celle d’entrée. Compilateur Intel ICC 8. Taux d’accélération est calculé par rapport à l’implémentation via pointer++ que nous prenons comme étalon (en gras).

TAB. 6.1 : Algorithmes de transposition par diagonale et antidiagonale ; comparaison des temps de calcul et

des taux d’accélération pour diverses implémentations et des tailles d’images

void inline Transpose8x8_SSE2(

Iu8vec8 & mm0, Iu8vec8 & mm1, Iu8vec8 & mm2, Iu8vec8 & mm3, Iu8vec8 & mm4, Iu8vec8 & mm5, Iu8vec8 & mm6, Iu8vec8 & mm7

) { __m128i xmm0, xmm1, xmm2, xmm3, __m128i xmm4, xmm5, xmm6, xmm7; xmm0 = _mm_movpi64_epi64( (__m64 &) mm0 ); xmm1 = _mm_movpi64_epi64( (__m64 &) mm1 ); xmm2 = _mm_movpi64_epi64( (__m64 &) mm2 ); xmm3 = _mm_movpi64_epi64( (__m64 &) mm3 ); xmm4 = _mm_movpi64_epi64( (__m64 &) mm4 ); xmm5 = _mm_movpi64_epi64( (__m64 &) mm5 ); xmm6 = _mm_movpi64_epi64( (__m64 &) mm6 ); xmm7 = _mm_movpi64_epi64( (__m64 &) mm7 ); xmm4 = _mm_unpacklo_epi8(xmm0, xmm4); xmm5 = _mm_unpacklo_epi8(xmm1, xmm5); xmm6 = _mm_unpacklo_epi8(xmm2, xmm6); xmm7 = _mm_unpacklo_epi8(xmm3, xmm7); xmm2 = xmm6; xmm2 = _mm_unpacklo_epi8(xmm4, xmm2); xmm3 = xmm7; xmm3 = _mm_unpacklo_epi8(xmm5, xmm3); xmm6 = _mm_unpackhi_epi8(xmm4, xmm6); xmm7 = _mm_unpackhi_epi8(xmm5, xmm7); xmm1 = xmm3; xmm1 = _mm_unpacklo_epi8(xmm2, xmm1); xmm3 = _mm_unpackhi_epi8(xmm2, xmm3); xmm5 = xmm7; xmm5 = _mm_unpacklo_epi8(xmm6, xmm5); xmm7 = _mm_unpackhi_epi8(xmm6, xmm7); (__m64 &)mm0 = _mm_movepi64_pi64(xmm1); xmm1 = _mm_srli_si128(xmm1, 8); (__m64 &)mm1 = _mm_movepi64_pi64(xmm1); (__m64 &)mm2 = _mm_movepi64_pi64(xmm3); xmm3 = _mm_srli_si128(xmm3, 8); (__m64 &)mm3 = _mm_movepi64_pi64(xmm3); (__m64 &)mm4 = _mm_movepi64_pi64(xmm5); xmm5 = _mm_srli_si128(xmm5, 8); (__m64 &)mm5 = _mm_movepi64_pi64(xmm5); (__m64 &)mm6 = _mm_movepi64_pi64(xmm7); xmm7 = _mm_srli_si128(xmm7, 8); (__m64 &)mm7 = _mm_movepi64_pi64(xmm7); _mm_empty(); return; }

FIG. 6.8 : Code de la transposition par diagonale d’un macro bloc 8 × 8 écrit manuellement en langage C en utilisant le jeu d’instructions 128 bits Intel SSE2

Le deuxième graphique de la même figure, 6.9(b), nous présente encore un comportement intéressant des processeurs sur les chiffres des temps d’exécution nor- malisés pour 1 pixel. Il s’agit de l’impact de la mémoire cache sur le calcul des images dont la taille excède celle de la mémoire cache. Il s’agit, dans ce cas précis, de la mémoire cache L2 de notre processeur Intel Pentium 4 et dont la taille est de 512 ko.

Il y a, en effet, deux points à remarquer. Première- ment, on voit bien que pour les images qui entrent en- tièrement dans la mémoire cache (images 1282, 2562et

5122), le coût du calcul est moindre à celui des images

qui n’y entrent pas (10242, 20482, 40962). Pour les

dernières, nous ne profitons pas d’un accès rapide aux données et le surcoût devrait correspondre au temps d’attente relative à la préparation des données non- présentes dans la mémoire cache.

Deuxièmement, nous pouvons apercevoir un com- portement particulier pour les images 10242, 20482,

40962, c’est-à-dire les images dont la taille est plus grande que celle de la mémoire cache L2. Pour ces dernières, l’écart entre les implémentations SIMD et non-SIMD est beaucoup plus important que pour les images qui entrent entièrement dans la mémoire cache. Pourtant, le surcoût des transferts des données entre la mémoire cache et la mémoire principale devrait être, en théorie, le même pour les deux manières d’implé- mentation, puisque le volume de données transférées est identique.

L’explication de ce comportement n’a pas pu être identifiée mais vu que les temps de traitement de- viennent importants pour les grandes images, nous n’excluons pas la possibilité que ce comportement soit

0,01 0,1 1 10 100 1000 10000 128x128x8 256x256x8 512x512x8 1024x1024x8 2048x2048x8 4096x4096x8 temps / ms SSE2 MMX p++ générique

(a)Temps du calcul

0,1 1 10 100 128x128x8 256x256x8 512x512x8 1024x1024x8 2048x2048x8 4096x4096x8 temps / ns SSE2 MMX p++ générique

(b)Temps du calcul normalisé pour 1 pixel

FIG. 6.9 : Résultats de diverses implémentations de la transposition par diagonale pour différentes tailles

d’images

drake 9.1 dans ce cas-là, à la manière de mesure du temps (plusieurs itérations, temps moyen) ou à un autre phénomène connexe à l’environnement d’exécution.