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