2010-07-09 4 views
7

La multiplication et la division complexes sont-elles bénéfiques grâce aux instructions SSE? Je sais que l'addition et la soustraction fonctionnent mieux lorsque vous utilisez SSE. Quelqu'un peut-il me dire comment je peux utiliser SSE pour effectuer une multiplication complexe pour obtenir de meilleures performances?Complexe Mul et Div utilisant sse Instructions

Répondre

9

bien multiplication complexe est défini comme:

((c1a * c2a) - (c1b * c2b)) + ((c1b * c2a) + (c1a * c2b))i 

Ainsi, vos 2 composants dans un nombre complexe seraient

((c1a * c2a) - (c1b * c2b)) and ((c1b * c2a) + (c1a * c2b))i 

Donc, en supposant que vous utilisez 8 flotteurs pour représenter 4 nombres complexes définis comme suit :

c1a, c1b, c2a, c2b 
c3a, c3b, c4a, c4b 

Et vous voulez faire simultanément (c1 * c3) et (c2 * c4) votre code SSE ressemblerait à "quelque chose" comme le suivant:

(Note J'ai utilisé MSVC sous windows mais le principe sera le même).

__declspec(align(16)) float c1c2[]  = { 1.0f, 2.0f, 3.0f, 4.0f }; 
__declspec(align(16)) float c3c4[]   = { 4.0f, 3.0f, 2.0f, 1.0f }; 
__declspec(align(16)) float mulfactors[] = { -1.0f, 1.0f, -1.0f, 1.0f }; 
__declspec(align(16)) float res[]   = { 0.0f, 0.0f, 0.0f, 0.0f }; 

__asm 
{ 
    movaps xmm0, xmmword ptr [c1c2]   // Load c1 and c2 into xmm0. 
    movaps xmm1, xmmword ptr [c3c4]   // Load c3 and c4 into xmm1. 
    movaps xmm4, xmmword ptr [mulfactors] // load multiplication factors into xmm4 

    movaps xmm2, xmm1      
    movaps xmm3, xmm0      
    shufps xmm2, xmm1, 0xA0     // Change order to c3a c3a c4a c4a and store in xmm2 
    shufps xmm1, xmm1, 0xF5     // Change order to c3b c3b c4b c4b and store in xmm1 
    shufps xmm3, xmm0, 0xB1     // change order to c1b c1a c2b c2a abd store in xmm3 

    mulps xmm0, xmm2       
    mulps xmm3, xmm1      
    mulps xmm3, xmm4      // Flip the signs of the 'a's so the add works correctly. 

    addps xmm0, xmm3      // Add together 

    movaps xmmword ptr [res], xmm0   // Store back out 
}; 

float res1a = (c1c2[0] * c3c4[0]) - (c1c2[1] * c3c4[1]); 
float res1b = (c1c2[1] * c3c4[0]) + (c1c2[0] * c3c4[1]); 

float res2a = (c1c2[2] * c3c4[2]) - (c1c2[3] * c3c4[3]); 
float res2b = (c1c2[3] * c3c4[2]) + (c1c2[2] * c3c4[3]); 

if (res1a != res[0] || 
    res1b != res[1] || 
    res2a != res[2] || 
    res2b != res[3]) 
{ 
    _exit(1); 
} 

Ce que j'ai fait ci-dessus, c'est que j'ai simplifié un peu les calculs. En supposant que les éléments suivants:

c1a c1b c2a c2b 
c3a c3b c4a c4b 

En réorganisant je finis avec les vecteurs suivants

0 => c1a c1b c2a c2b 
1 => c3b c3b c4b c4b 
2 => c3a c3a c4a c4a 
3 => c1b c1a c2b c2a 

Je puis multiplier 0 et 2 ensemble pour obtenir:

0 => c1a * c3a, c1b * c3a, c2a * c4a, c2b * c4a 

Ensuite, je multiplierai 3 et 1 ensemble pour obtenir:

Enfin je feuillette les signes d'un couple des flotteurs dans 3

3 => -(c1b * c3b), c1a * c3b, -(c2b * c4b), c2a * c4b 

Je peux les ajouter ensemble et obtenir

(c1a * c3a) - (c1b * c3b), (c1b * c3a) + (c1a * c3b), (c2a * c4a) - (c2b * c4b), (c2b * c4a) + (c2a * c4b) 

Ce qui est ce que nous recherchions :)

+0

Voir aussi http: // logiciel .intel.com/file/1000, qui semble avoir un algorithme encore plus rapide. – MSalters

+1

Ouais le même type de configuration que le mien mais leur SSE3 nécessite ... ce qui est 99% du temps OK à ce jour, je l'admets. – Goz

+0

Cette instruction addubps semble très utile. Hélas je ne cible généralement pas au-dessus de SSE2 pour des raisons de compatibilité :( – Goz

8

Juste pour l'exhaustivité, le Manuel de référence de l'optimisation des architectures Intel® 64 et IA-32 téléchargeable here contient un assemblage pour la division complexe (exemple 6-9) et complexe (exemple 6-10).

est ici par exemple le code se multiplient:

// Multiplication of (ak + i bk) * (ck + i dk) 
// a + i b can be stored as a data structure 
movsldup xmm0, src1; load real parts into the destination, a1, a1, a0, a0 
movaps xmm1, src2; load the 2nd pair of complex values, i.e. d1, c1, d0, c0 
mulps xmm0, xmm1; temporary results, a1d1, a1c1, a0d0, a0c0 
shufps xmm1, xmm1, b1; reorder the real and imaginary parts, c1, d1, c0, d0 
movshdup xmm2, src1; load imaginary parts into the destination, b1, b1, b0, b0 
mulps xmm2, xmm1; temporary results, b1c1, b1d1, b0c0, b0d0 
addsubps xmm0, xmm2; b1c1+a1d1, a1c1 -b1d1, b0c0+a0d0, ; a0c0-b0d0 

L'ensemble des cartes directement à gccs X86 intrinsics (juste prédicat chaque instruction avec __builtin_ia32_).

4

L'algorithme de la référence d'optimisation Intel ne gère pas correctement les débordements et NaN dans l'entrée.

Un seul NaN dans la partie réelle ou imaginaire du numéro se propagera incorrectement à l'autre partie.

Étant donné que plusieurs opérations avec l'infini (par exemple, l'infini * 0) se terminent par NaN, les débordements peuvent provoquer l'apparition de NaN s dans vos données par ailleurs saines.

Si déversoirs et NaN s sont rares, d'une manière simple d'éviter cela est de vérifier juste pour NaN dans le résultat et recalcule avec les compilateurs mise en œuvre conforme IEEE:

float complex a[2], b[2]; 
__m128 res = simd_fast_multiply(a, b); 

/* store unconditionally, can be executed in parallel with the check 
* making it almost free if there is no NaN in data */ 
_mm_store_ps(dest, res); 

/* check for NaN */ 
__m128 n = _mm_cmpneq_ps(res, res); 
int have_nan = _mm_movemask_ps(n); 
if (have_nan != 0) { 
    /* do it again unvectorized */ 
    dest[0] = a[0] * b[0]; 
    dest[1] = a[1] * b[1]; 
} 
Questions connexes