2010-05-23 2 views
1

J'ai une boucle d'assembleur en ligne qui ajoute de manière cumulative des éléments d'un tableau de données int32 avec des instructions MMX. En particulier, il utilise le fait que les registres MMX peuvent accueillir 16 int32s pour calculer 16 sommes cumulées différentes en parallèle. Je voudrais maintenant convertir ce morceau de code en intrinsèques MMX mais je crains de subir une pénalité de performance car on ne peut pas explicitement intruder le compilateur pour utiliser les 8 registres MMX pour accomuler 16 sommes indépendantes.utilisation de la pile avec MMX et Microsoft intrinsics C++

Quelqu'un peut-il commenter cela et peut-être proposer une solution sur la façon de convertir le morceau de code ci-dessous pour utiliser intrinsics?

assembleur inline == (seulement partiel à l'intérieur de la boucle) ==

paddd mm0, [esi+edx+8*0] ; add first & second pair of int32 elements 
paddd mm1, [esi+edx+8*1] ; add third & fourth pair of int32 elements ... 
paddd mm2, [esi+edx+8*2] 
paddd mm3, [esi+edx+8*3] 
paddd mm4, [esi+edx+8*4] 
paddd mm5, [esi+edx+8*5] 
paddd mm6, [esi+edx+8*6] 
paddd mm7, [esi+edx+8*7] ; add 15th & 16th pair of int32 elements 
  • points de esi au début de la matrice de données
  • EDX fournit le décalage dans le réseau de données pour la boucle de courant itération
  • la matrice de données est agencé de telle sorte que les éléments pour les 16 montants indépendants sont entrelacées.

Répondre

2

Le VS2010 fait un travail d'optimisation décent sur le code équivalent en utilisant intrinsics. Dans la plupart des cas, il compile la valeur intrinsèque:

sum = _mm_add_pi32(sum, *(__m64 *) &intArray[i + offset]); 

en quelque chose comme:

movq mm0, mmword ptr [eax+8*offset] 
paddd mm1, mm0 

Ce n'est pas aussi concis que votre padd mm1, [esi+edx+8*offset], mais il est sans doute assez proche. Le temps d'exécution est probablement dominé par l'extraction de la mémoire.

Le hic est que VS semble comme l'ajout d'MMX enregistre uniquement à d'autres registres MMX. Le schéma ci-dessus ne fonctionne que pour les 7 premières sommes. La 8ème somme nécessite que certains registres soient enregistrés temporairement en mémoire.

Voici un programme complet et son ensemble compilé (release build) correspondant:

#include <stdio.h> 
#include <stdlib.h> 
#include <xmmintrin.h> 

void addWithInterleavedIntrinsics(int *interleaved, int count) 
{ 
    // sum up the numbers 
    __m64 sum0 = _mm_setzero_si64(), sum1 = _mm_setzero_si64(), 
      sum2 = _mm_setzero_si64(), sum3 = _mm_setzero_si64(), 
      sum4 = _mm_setzero_si64(), sum5 = _mm_setzero_si64(), 
      sum6 = _mm_setzero_si64(), sum7 = _mm_setzero_si64(); 

    for (int i = 0; i < 16 * count; i += 16) { 
     sum0 = _mm_add_pi32(sum0, *(__m64 *) &interleaved[i]); 
     sum1 = _mm_add_pi32(sum1, *(__m64 *) &interleaved[i + 2]); 
     sum2 = _mm_add_pi32(sum2, *(__m64 *) &interleaved[i + 4]); 
     sum3 = _mm_add_pi32(sum3, *(__m64 *) &interleaved[i + 6]); 
     sum4 = _mm_add_pi32(sum4, *(__m64 *) &interleaved[i + 8]); 
     sum5 = _mm_add_pi32(sum5, *(__m64 *) &interleaved[i + 10]); 
     sum6 = _mm_add_pi32(sum6, *(__m64 *) &interleaved[i + 12]); 
     sum7 = _mm_add_pi32(sum7, *(__m64 *) &interleaved[i + 14]); 
    } 

    // reset the MMX/floating-point state 
    _mm_empty(); 

    // write out the sums; we have to do something with the sums so that 
    // the optimizer doesn't just decide to avoid computing them. 
    printf("%.8x %.8x\n", ((int *) &sum0)[0], ((int *) &sum0)[1]); 
    printf("%.8x %.8x\n", ((int *) &sum1)[0], ((int *) &sum1)[1]); 
    printf("%.8x %.8x\n", ((int *) &sum2)[0], ((int *) &sum2)[1]); 
    printf("%.8x %.8x\n", ((int *) &sum3)[0], ((int *) &sum3)[1]); 
    printf("%.8x %.8x\n", ((int *) &sum4)[0], ((int *) &sum4)[1]); 
    printf("%.8x %.8x\n", ((int *) &sum5)[0], ((int *) &sum5)[1]); 
    printf("%.8x %.8x\n", ((int *) &sum6)[0], ((int *) &sum6)[1]); 
    printf("%.8x %.8x\n", ((int *) &sum7)[0], ((int *) &sum7)[1]); 
} 

void main() 
{ 
    int count  = 10000; 
    int *interleaved = new int[16 * count]; 

    // create some random numbers to add up 
    // (note that on VS2010, RAND_MAX is just 32767) 
    for (int i = 0; i < 16 * count; ++i) { 
     interleaved[i] = rand(); 
    } 

    addWithInterleavedIntrinsics(interleaved, count); 
} 

Voici le code assembleur généré pour la partie interne de la boucle de somme (sans son prologue et épilogue). Notez comment la plupart des sommes sont conservées efficacement en mm1-mm6. Contraste avec mm0, qui est utilisé pour amener le nombre à ajouter à chaque somme, et avec mm7, qui sert les deux dernières sommes. La version 7-somme de ce programme ne semble pas avoir le problème mm7.

012D1070 movq  mm7,mmword ptr [esp+18h] 
012D1075 movq  mm0,mmword ptr [eax-10h] 
012D1079 paddd  mm1,mm0 
012D107C movq  mm0,mmword ptr [eax-8] 
012D1080 paddd  mm2,mm0 
012D1083 movq  mm0,mmword ptr [eax] 
012D1086 paddd  mm3,mm0 
012D1089 movq  mm0,mmword ptr [eax+8] 
012D108D paddd  mm4,mm0 
012D1090 movq  mm0,mmword ptr [eax+10h] 
012D1094 paddd  mm5,mm0 
012D1097 movq  mm0,mmword ptr [eax+18h] 
012D109B paddd  mm6,mm0 
012D109E movq  mm0,mmword ptr [eax+20h] 
012D10A2 paddd  mm7,mm0 
012D10A5 movq  mmword ptr [esp+18h],mm7 
012D10AA movq  mm0,mmword ptr [esp+10h] 
012D10AF movq  mm7,mmword ptr [eax+28h] 
012D10B3 add   eax,40h 
012D10B6 dec   ecx 
012D10B7 paddd  mm0,mm7 
012D10BA movq  mmword ptr [esp+10h],mm0 
012D10BF jne   main+70h (12D1070h) 

Alors, que pouvez-vous faire?

  1. Affichent les programmes basés sur des intrinsèques à somme de somme et à somme de 8. Choisissez celui qui s'exécute plus rapidement.

  2. Profil de la version qui ajoute qu'un registre MMX à la fois. Il devrait encore être en mesure de tirer parti du fait que les processeurs modernes fetch 64 to 128 bytes into the cache at a time. Il n'est pas évident que la version 8-somme serait plus rapide que la version 1-somme. La version 1-sum récupère exactement la même quantité de mémoire, et fait exactement le même nombre d'additions MMX. Vous devrez cependant entrelacer les entrées en conséquence.Si votre matériel cible le permet, utilisez SSE instructions. Ceux-ci peuvent ajouter 4 valeurs de 32 bits à la fois. SSE est disponible dans les processeurs Intel depuis le Pentium III en 1999.

Questions connexes