| 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.
# 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 | ================================== |