SSE4: DPPS — iloczyn skalarny

Autor: Wojciech Muła
Dodany:13.09.2007

Rozkaz dpps $imm8, %xmm2, %xmm1 działa na wektorach liczb zmiennoprzecinkowych pojedynczej precyzji, oblicza iloczy skalarny. Realizuje następujący algorytm:

{ maska imm8[7:4] określa które elementy wejdą do wyniku }
dot := 0.0
for i:=0 to 3 do
   if imm8[i + 4] then
     dot := dot + (xmm1[i] * xmm2[i])

{ maska imm8[3:0] określa co zostanie zapisane na kolejnych
  elementach xmm1, tj. albo wynik albo 0.0 }
for i:=0 to 3 do
   if imm8[i] then
     xmm1[i] := dot
   else
     xmm1[i] := 0.0

Czyli nie jest to jakaś beznadziejnie prymitywna instrukcja, ale całkiem potężna. Dzięki łatwemu obliczaniu iloczynu skalarnego, równie łatwo można wykonywać mnożenie macierzy 4x4 przez wektor 4x1 oraz mnożenie dwóch macierzy 4x4. Przedstawiam tutaj kod mnożenia macierzy i wektora.

sse4-matvec-mult.S:

# void sse4_matvec_mult(float mat[4][4], float vec[4], float result[4]);
sse4_matvec_mult:
        mov      4(%esp), %eax  # mat[0][0] address
        mov      8(%esp), %edx  # vec address

        # load matrix                    column -> 0  1  2  3
        movups  0x00(%eax), %xmm0       # xmm0 := a0 a1 a2 a3  A row
        movups  0x10(%eax), %xmm1       # xmm1 := b0 b1 b2 b3  B  |
        movups  0x20(%eax), %xmm2       # xmm2 := c0 c1 c2 c3  C  v
        movups  0x30(%eax), %xmm3       # xmm3 := d0 d1 d2 d3  D

        # load vector
        movups  (%edx), %xmm4           # xmm4 := X0 X1 X2 X3

        # calculate result, i.e. get dot products
        # of input vector and all rows
        dpps    $0b11110001, %xmm4, %xmm0       # xmm0 := | dotA |  0   |  0   |  0   |
        dpps    $0b11110010, %xmm4, %xmm1       # xmm1 := |  0   | dotB |  0   |  0   |
        dpps    $0b11110100, %xmm4, %xmm2       # xmm2 := |  0   |  0   | dotC |  0   |
        dpps    $0b11111000, %xmm4, %xmm3       # xmm3 := |  0   |  0   |  0   | dotD |

        orps    %xmm1, %xmm0    # xmm0 := | dotA | dotB |  0   |  0   |
        orps    %xmm2, %xmm3    # xmm3 := |  0   |  0   | dotC | dotD |
        orps    %xmm3, %xmm0    # xmm0 := | dotA | dotB | dotC | dotD |

        # save result, i.e. xmm0
        mov     12(%esp), %eax  # result address
        movups  %xmm0, (%eax)

        ret

Dopisek z 28.06.2008: program sse-matvecmult.c zawiera implementacje tej procedury oraz procedury wykorzystującej rozkaz HADDPS (SSE3), jak również rozkazy FPU (generowane przez kompilator). Rozkaz DPPS ma bardzo długie opóźnienie 11 cykli — dla pojedynczych mnożeń szybszy jest kod SSE3. Wyniki testów przeprowadzonych na Core2 Duo E8200:

procedura czas [s] przyspieszenie  
FPU 1.348 1.0 ====================
SSE3 0.592 2.2 ============================================
SSE4 0.764 1.7 ==================================