Streaming SIMD Extensions — przykłady zastosowań

Autor: Wojciech Muła
Dodany:13.03.2002
Aktualizacja:15.09.2007

Treść

Wprowadzenie

Po opis rozkazów odsyłam do dokumentacji procesora. Rozkazy SSE umożliwiają obliczenia na liczbach zmiennoprzecinkowych pojedynczej precyzji (4 bajtowych). Przy korzystaniu z SSE bardzo istotne jest wyrównanie danych — większość rozkazów domyślnie przyjmuje, że dane są na granicy 16 bajtów — gdy tak nie jest generowany jest wyjątek #GP.

Procesory 32-bitowe posiadają osiem, natomiast 64-bitowe szesnaście rejestrów SSE, każdy o rozmiarze 128 bitów (4 x liczba zmiennoprzecinkowa). Ich nazwy w asemblerze to: xmm0, xmm1, ... xmm15.

Uwaga: w komentarzach „?” oznacza nieważny, używany jest do oznaczenia nieistotnych wartości w rejestrach SSE.

Iloczyn skalarny

Iloczyn skalarny (ang. dot product) dwóch wektorów 3D V1 = [x1, y1, y1] i V2 = [x2, y2, y2] dany jest wzorem:

DP = x1x2 + y1y2 + z1z2

sse-dotprod.c:


void sse_dot(float vec1[4], float vec2[4], float* result) {
__asm__ volatile (
   "movups (%0), %%xmm0                    \n" // load vec1: |w1|z1|y1|x1|
   "movups (%1), %%xmm1                    \n" // load vec2: |w2|z2|y2|x2|
   "                                       \n"
   "mulps   %%xmm1, %%xmm0                 \n" // xmm0 := |w1*w2|z1*z2|y1*y2|x1*x2|
   "movhlps %%xmm0, %%xmm1                 \n" // xmm1 := |  .  |  .  |w1*w2|z1*z2|
   "addps   %%xmm0, %%xmm1                 \n" // xmm1 := |  .  |  .  | w+y | z+x |
   "movaps  %%xmm1, %%xmm0                 \n" // save xmm1
   "shufps  $0x01,  %%xmm1, %%xmm1         \n" // xmm1 := |  .  |  .  |  .  | w+y |
   "addss   %%xmm1, %%xmm0                 \n" // xmm0[0] := dot product
   "movss   %%xmm0, (%2)                   \n"
   :
   : "r" (vec1), "r" (vec2), "r" (result));
}

Żeby jednak w 100% wykorzystać moc obliczeniową, należy liczyć równocześnie 4 iloczyny skalarne.

sse-dotprod4.c:


void sse_dot4(float* v1x4, float* v2x4, float* results) {
__asm__ volatile (
   // load 4 vectors from v1
   "movups    (%0), %%xmm0                 \n"
   "movups  16(%0), %%xmm1                 \n"
   "movups  32(%0), %%xmm2                 \n"
   "movups  48(%0), %%xmm3                 \n"
   
   // load 4 vectors from v2
   "movups    (%1), %%xmm4                 \n"
   "movups  16(%1), %%xmm5                 \n"
   "movups  32(%1), %%xmm6                 \n"
   "movups  48(%1), %%xmm7                 \n"
   
   // perfom parallel multiplications
   "mulps   %%xmm4, %%xmm0                 \n" // xmm0 := |A3|A2|A1|A0|
   "mulps   %%xmm5, %%xmm1                 \n" // xmm1 := |B3|B2|B1|B0|
   "mulps   %%xmm6, %%xmm2                 \n" // xmm2 := |C3|C2|C1|C0|
   "mulps   %%xmm7, %%xmm3                 \n" // xmm3 := |D3|D2|D1|D0|
   // (xmm4-xmm7 are free)
   
   // perfom additions
   "movaps   %%xmm0, %%xmm4                \n"
   "unpcklps %%xmm1, %%xmm0                \n" // xmm0 := |B1|A1|B0|A0|
   "unpckhps %%xmm1, %%xmm4                \n" // xmm4 := |B3|A3|B2|A2| (xmm1 is free)
   "movaps   %%xmm2, %%xmm1                \n"
   "unpcklps %%xmm3, %%xmm2                \n" // xmm2 := |D1|C1|D0|C0|
   "unpckhps %%xmm3, %%xmm1                \n" // xmm1 := |D3|C3|D2|C2| (xmm3 is free)
   "addps    %%xmm4, %%xmm0                \n" // xmm0 := | B13 | A13 | B02 | A02 | (xmm1 is free)
   "addps    %%xmm1, %%xmm2                \n" // xmm2 := | D13 | C13 | D02 | C02 | (xmm3 is free)
   
   "movaps   %%xmm0, %%xmm1                \n"
   "shufps   $0b01000100, %%xmm2, %%xmm0   \n" // xmm0 := | D02 | C02 | B02 | A02 |
   "shufps   $0b11101110, %%xmm2, %%xmm1   \n" // xmm1 := | D13 | C13 | B13 | A13 |
   "addps    %%xmm1, %%xmm0                \n" // xmm0 := |D0123|C0123|B0123|A0123|
   "movups   %%xmm0, (%2)                  \n"
   :
   : "r" (v1x4), "r" (v2x4), "r" (results)
   );
}

Iloczyn wektorowy

Wynikiem iloczynu wektorowego (ang. cross product) dwóch wektorów jest wektor prostopadły (normalny) do płaszczyzny na której znajdują się te wektory. Iloczyn wektorowy liczy się z wyznacznika:

|i j k|
|a b c| = i(b*z-c*y) - j(a*z-c*x) + k(a*y-b*x)
|x y z|

        = [b*z - c*y, c*x - a*z, a*y - b*x] =

        = [b*z, c*x, a*y] - [c*y, a*z, b*x]

gdzie i, j, k — wersory osi, w uładzie kartezjańskim i = [1, 0, 0], j = [0, 1, 0], oraz k = [0, 0, 1].

sse-crossprod.c:


void sse_cross_prod(float v1[4], float v2[4], float result[4]) {
__asm__ volatile (
    // load vectors                                 index -> 0   1   2   3
    "movups (%0), %%xmm0                    \n" // xmm0 := | a | b | c | . |
    "movups (%1), %%xmm1                    \n" // xmm1 := | x | y | z | . |
    
    // clone
    "movaps %%xmm0, %%xmm2                  \n"
    "movaps %%xmm1, %%xmm3                  \n"

    // shuffle
    "shufps $0b00001001, %%xmm0, %%xmm0     \n" // xmm0 := | b | c | a | . |
    "shufps $0b00010010, %%xmm1, %%xmm1     \n" // xmm1 := | z | x | y | . |
    
    "shufps $0b00010010, %%xmm2, %%xmm2     \n" // xmm2 := | c | a | b | . |
    "shufps $0b00001001, %%xmm3, %%xmm3     \n" // xmm3 := | y | z | x | . |

    // multiply
    "mulps %%xmm1, %%xmm0                   \n" // xmm0 := |b*z|c*x|a*y| . |
    "mulps %%xmm3, %%xmm2                   \n" // xmm2 := |c*y|a*z|b*x| . |

    // and finally subtract
    "subps %%xmm2, %%xmm0                   \n" // xmm0 := |b*z - c*y|c*x - a*z|a*y - b*x| ... |
    "movups %%xmm0, (%2)                    \n"
    :
    : "r" (v1), "r" (v2), "r" (result)
);
}

Normalizacja wektora

Wynikiem normalizacja wektora n-wymiarowego jest wektor o długości 1 i o takich samych cosinusach kierunkowych jak wektor wejściowy. Wzór jest następujący:

V' = V/|V|

; 11.03.2002
;
; edi -> wektor wejściowy

movups xmm0, [edi] ; załaduj wektor

movaps xmm1, xmm0  ; xmm1 = |  0  |  z  |  y  |  x  |
mulps  xmm1, xmm0  ; xmm1 = |  0  | z*z | y*y | x*x |

movaps xmm2, xmm1
movaps xmm3, xmm1

shufps xmm2, xmm1, 0fdh ; xmm2 = |  0  |  0  |  0  | y^2 |
shufps xmm3, xmm1, 0feh ; xmm3 = |  0  |  0  |  0  | z^2 |

addps  xmm1, xmm2       ;
addps  xmm1, xmm3       ; xmm1 = |  0  |  nu |  nu | x^2+y^2+z^2 |

sqrtss xmm1             ; xmm1 = |  0  |  nu | nu | sqrt(x^2+y^2+z^2) |
                        ;                                  len

shufps xmm1, xmm1, 000h ; xmm1 = | len | len | len | len |

divps  xmm0, xmm1       ; xmm0 = |  0  |z/len|y/len|x/len| -- wynik

normvecSSE.asm

Obrót punktu 2D

Obrót punktu o kąt fi wokół środka układu współrzędnych dany jest wzorem:

x' = xcos(fi) − ysin(fi) y' = xsin(fi) + ycos(fi)

; 11.03.2002

segment .data

align 16
sincos:
        dd ? ; sinus(fi)
        dd ? ; cosinus(fi)

vector:
        dd ? ; x
        dd ? ; y

negate:
        dd 0
        dd 0
        dd 0x80000000
        dd 0

segment .text

movlps   xmm0, [vector]   ; xmm0 = | ? | ? | y | x |
movlps   xmm1, [sincos]   ; xmm1 = | ? | ? | c | s |

unpcklps xmm0, xmm0       ; xmm0 = | y | y | x | x |
shufps   xmm1, xmm1, 044h ; xmm1 = | c | s | c | s |
xorps    xmm1, [negate]   ; xmm1 = | c |-s | c | s |

mulps    xmm0, xmm1       ; xmm0 = | y*c  | -y*s | x*c | x*s |

movaps   xmm1, xmm0
shufps   xmm1, xmm1, 00eh ; xmm1 = |  nu  |  nu  |-y*s | y*c |

addps    xmm0, xmm1       ; xmm0 = |  nu  |  nu  |xc-ys|xs+yc| -- wynik

Mnożenie liczb zespolonych

Wynikiem mnożenia liczb zespolonych (a + ib) i (c + id) jest liczba zespolona (acbd) + i(bc + ad).

; 13-03-02
; edi -> (a+ib) (typ float[2])
; esi -> (c+id) (typ float[2])
; edx -> wynik

segment .data

__negate:
          dd 0
          dd 0
          dd 0
          dd 0x80000000

segment .text

movlps xmm0, [edi]        ; xmm0 = | nu  | nu  |  b  |  a  |
movlps xmm1, [esi]        ; xmm1 = | nu  | nu  |  d  |  c  |

shufps   xmm0, xmm0, 044h ; xmm0 = |  b  |  a  |  b  |  a  |

unpcklps xmm1, xmm1       ; xmm1 = |  d  |  d  |  c  |  c  |
xorps    xmm1, [__negate] ; xmm1 = | -d  |  d  |  c  |  c  |

mulps    xmm0, xmm1       ; xmm0 = | -bd |  ad |  bc |  ac |

movaps   xmm1, xmm0
shufps   xmm1, xmm1, 00bh ; xmm1 = |  nu |  nu |  ad | -bd |

addps    xmm0, xmm1       ; xmm0 = |  nu |  nu |ad+bc|ac-bd|

movlps  [edx], xmm0

Mnożenie macierzy (row-majority) przez wektor

Mnożenie macierzy 4x4 przez wektor 4x1:

[a0, a1, a2, a3]   [x]   [a0*x + a1*y + a*z + a3]
[b0, b1, b2, b3]   [y]   [b0*x + b1*y + b*z + b3]
[              ] * [ ] = [                      ]
[c0, c1, c2, c3]   [z]   [c0*x + c1*y + c*z + c3]
[d0, d1, d2, d3]   [w]   [d0*x + d1*y + d*z + d3]

sse-matvec-mult.c:


void sse_matvec_mult(float mat[4*4], float vec[4], float result[4]) {
__asm__ volatile (
   // load vector
   "movups (%1), %%xmm4                    \n" // xmm4 := [x, y, w, z] = V

   // load matrix
   "movups 0x00(%0), %%xmm0                \n" // xmm0 := [a0, a1, a2, a3] = A
   "movups 0x10(%0), %%xmm1                \n" // xmm1 := [b0, b1, b2, b3] = B
   "movups 0x20(%0), %%xmm2                \n" // xmm2 := [c0, c1, c2, c3] = C
   "movups 0x30(%0), %%xmm3                \n" // xmm3 := [d0, d1, d2, d3] = D

   // multiply all rows by vector
   "mulps %%xmm4, %%xmm0                   \n" // xmm0 := A*V
   "mulps %%xmm4, %%xmm1                   \n" // xmm1 := B*V
   "mulps %%xmm4, %%xmm2                   \n" // xmm2 := C*V
   "mulps %%xmm4, %%xmm3                   \n" // xmm3 := D*V

   // finally calc dot products
   "movaps   %%xmm0, %%xmm4                \n" // save A
   "unpcklps %%xmm1, %%xmm0                \n" // xmm0 := [a0, b0, a1, b1]
   "unpckhps %%xmm1, %%xmm4                \n" // xmm4 := [a2, b2, a3, b3] (xmm1 is free)
   "                                       \n"
   "movaps   %%xmm2, %%xmm1                \n" // save C
   "unpcklps %%xmm3, %%xmm2                \n" // xmm2 := [c0, d0, c1, d1]
   "unpckhps %%xmm3, %%xmm1                \n" // xmm1 := [c2, d2, c3, d3] (xmm3 is free)
   "                                       \n"
   "addps    %%xmm4, %%xmm0                \n" // xmm0 := [a0+a2, b0+b2, a1+a3, b1+b3] (xmm4 is free)
   "addps    %%xmm2, %%xmm1                \n" // xmm1 := [c0+c2, bd+d2, c1+c3, d1+d3] (xmm2 is free)
   "                                       \n"
   "movaps   %%xmm0, %%xmm4                \n"
   "shufps $0b01000100, %%xmm1, %%xmm0     \n" // xmm0 := [a02, b02, c02, d02 ]
   "shufps $0b11101110, %%xmm1, %%xmm4     \n" // xmm4 := [a13, b13, c13, d13 ]
   "                                       \n"
   "addps    %%xmm4, %%xmm0                \n" // xmm0 := [a0123, b0123, c0123, d0123]
   "movups   %%xmm0, (%2)                  \n"
   "0:                                     \n"
   :
   : "r" (mat), "r" (vec), "r" (result)
   : "memory"
);
}

Mnożenie wektora przez macierz (column-majority)

Mnożenie wektora 1x4 przez macierz 4x4:

               [a0, a1, a2, a3]   [x*a0 + y*b0 + z*c0 + w*d0]
               [b0, b1, b2, b3]   [x*a1 + y*b1 + z*c1 + w*d1]
[x, y, z, w] * [              ] = [                         ]
               [c0, c1, c2, c3]   [x*a2 + y*b2 + z*c2 + w*d2]
               [d0, d1, d2, d3]   [x*a3 + y*b3 + z*c3 + w*d3]

sse-vecmat-mult.c:


void sse_vecmat_mult(float mat[4*4], float vec[4], float result[4]) {
__asm__ volatile (
   // load vector
   "movups (%1), %%xmm4                    \n" // xmm4 := [x, y, z, w] = V
   "movaps %%xmm4, %%xmm5                  \n"
   "movaps %%xmm4, %%xmm6                  \n"
   "movaps %%xmm4, %%xmm7                  \n"

   // load matrix
   "movups 0x00(%0), %%xmm0                \n" // xmm0 := [a0, a1, a2, a3] = A
   "movups 0x10(%0), %%xmm1                \n" // xmm1 := [b0, b1, b2, b3] = B
   "movups 0x20(%0), %%xmm2                \n" // xmm2 := [c0, c1, c2, c3] = C
   "movups 0x30(%0), %%xmm3                \n" // xmm3 := [d0, d1, d2, d3] = D

   // populate coords in vectors
   "shufps $0b00000000, %%xmm4, %%xmm4     \n" // xmm4 := [x, x, x, x]
   "shufps $0b01010101, %%xmm5, %%xmm5     \n" // xmm5 := [y, y, y, y]
   "shufps $0b10101010, %%xmm6, %%xmm6     \n" // xmm6 := [z, z, z, z]
   "shufps $0b11111111, %%xmm7, %%xmm7     \n" // xmm7 := [w, w, w, w]

   // multiply all rows by vector
   "mulps %%xmm4, %%xmm0                   \n" // xmm0 := [x*a0, x*a1, x*a2, x*a3]
   "mulps %%xmm5, %%xmm1                   \n" // xmm1 := [y*b0, y*b1, y*b2, y*b3]
   "mulps %%xmm6, %%xmm2                   \n" // xmm2 := [z*c0, z*c1, z*c2, z*c3]
   "mulps %%xmm7, %%xmm3                   \n" // xmm3 := [w*d0, w*d1, w*d2, w*d3]

   // finally calc dot products
   "addps %%xmm1, %%xmm0                   \n"
   "addps %%xmm3, %%xmm2                   \n"
   "addps %%xmm2, %%xmm0                   \n"

   // save result
   "movups %%xmm0, (%2)                    \n"
   :
   : "r" (mat), "r" (vec), "r" (result)
   : "memory"
);
}

Transpozycja macierzy

Transpozycja macierzy to — z technicznego punktu widzenia — zamiana wierszy z wektorami, tj.:

[a0 a1 a2 a3]   [a0 b0 c0 d0]
[b0 b1 b2 b3] > [a1 b1 c1 d1]
[d0 d1 d2 d3] > [a2 b2 c2 d2]
[c1 c1 c2 c3]   [a3 b3 c3 d3]

     M               M^T

sse-transpose.c:


void sse_transpose(float mat[4*4]) {
__asm__ volatile (
                                //         0  1  2  3 <- index
   "movups 0x00(%0), %%xmm0 \n" // xmm0 := a0 a1 a2 a3
   "movups 0x10(%0), %%xmm1 \n" // xmm1 := b0 b1 b2 b3
   "movups 0x20(%0), %%xmm2 \n" // xmm2 := c0 c1 c2 c3
   "movups 0x30(%0), %%xmm3 \n" // xmm3 := d0 d1 d2 d3
   
   "movaps   %%xmm0, %%xmm4 \n"
   "unpcklps %%xmm2, %%xmm0 \n" // xmm0 := a0 c0 a1 c1
   "unpckhps %%xmm2, %%xmm4 \n" // xmm4 := a2 c2 a3 c3
   
   "movaps   %%xmm1, %%xmm2 \n"
   "unpcklps %%xmm3, %%xmm1 \n" // xmm1 := b0 d0 b1 d1
   "unpckhps %%xmm3, %%xmm2 \n" // xmm2 := b2 d2 b3 d3
   
   "movaps   %%xmm0, %%xmm3 \n"
   "unpcklps %%xmm1, %%xmm0 \n" // xmm0 := a0 b0 c0 d0
   "unpckhps %%xmm1, %%xmm3 \n" // xmm3 := a1 b1 c1 d1
   
   "movaps   %%xmm4, %%xmm1 \n"
   "unpcklps %%xmm2, %%xmm1 \n" // xmm1 := a2 b2 c2 d2
   "unpckhps %%xmm2, %%xmm4 \n" // xmm4 := a3 b3 c3 d3
   
   "movups %%xmm0, 0x00(%0) \n"
   "movups %%xmm3, 0x10(%0) \n"
   "movups %%xmm1, 0x20(%0) \n"
   "movups %%xmm4, 0x30(%0) \n"
   : "=r" (mat)
   : "0" (mat)
   : "memory"
);
}

Mnożenie macierzy 4x4

Poniższa procedura jest szybsza o ok. 2,5 raza od procedury napisanej w języku C i skompilowanej przez GCC z opcjami -O2 -ffast-math.

sse-matmult.c:


void sse_matmat_mult(float mat1[4*4], float mat2[4*4], float mat3[4*4]) {
__asm__ volatile (
    // load all rows from M2, i.e. Ba, Bb, Bc an Bd
    "movups 0x00(%1), %%xmm4                \n" // xmm4 := Ba
    "movups 0x10(%1), %%xmm5                \n" // xmm5 := Bb
    "movups 0x20(%1), %%xmm6                \n" // xmm6 := Bc
    "movups 0x30(%1), %%xmm7                \n" // xmm7 := Bd

#define BLOCK(offset) \
    /* load n-th row from matrix M1, for example Aa, and clone it */ \
    "movups "#offset"(%0), %%xmm0           \n" /* xmm0 := Aa */ \
    "movaps %%xmm0, %%xmm1                  \n" \
    "movaps %%xmm0, %%xmm2                  \n" \
    "movaps %%xmm0, %%xmm3                  \n" \
    /* populate each element */ \
    "shufps $0b00000000, %%xmm0, %%xmm0     \n" /* xmm0 := [Aa0, Aa0, Aa0, Aa0] */ \
    "shufps $0b01010101, %%xmm1, %%xmm1     \n" /* xmm1 := [Aa1, Aa1, Aa1, Aa1] */ \
    "shufps $0b10101010, %%xmm2, %%xmm2     \n" /* xmm2 := [Aa2, Aa2, Aa2, Aa2] */ \
    "shufps $0b11111111, %%xmm3, %%xmm3     \n" /* xmm3 := [Aa3, Aa3, Aa3, Aa3] */ \
    /* and mul by all M2 rows */ \
    "mulps %%xmm4, %%xmm0                   \n" /* xmm0 := [Aa0*Ba0, Aa0*Ba1, Aa0*Ba2, Aa0*Ba3] */ \
    "mulps %%xmm5, %%xmm1                   \n" \
    "mulps %%xmm6, %%xmm2                   \n" \
    "mulps %%xmm7, %%xmm3                   \n" \
    /* finally calculate n-th row of resultant matrix */ \
    "addps %%xmm1, %%xmm0                   \n" \
    "addps %%xmm3, %%xmm2                   \n" \
    "addps %%xmm2, %%xmm0                   \n" \
    "movups %%xmm0, "#offset"(%2)           \n"

    BLOCK(0x00)
    BLOCK(0x10)
    BLOCK(0x20)
    BLOCK(0x30)
    :
    : "r" (mat1), "r" (mat2), "r" (mat3)
);
}