Porównywanie liczb w SSE
Porównania skalarne ustawiające flagi procesora
Poniższe instrukcje porównują skalarnie ostatnie elementy rejestrów i ustawiają odpowiednie flagi procesora ZF
, PF
, CF
(resztę zerują), w ten sposób, że możemy następnie wykonywać odpwiednie skoki warunkowe jak dla liczb całkowitych bez znaku (JE
, JA
, JB
, JAE
,…)
comiss/comisd xmm1, xmm2 ; sygnalizuje błąd gdy argumentem jest QNaN lub SNaN
ucomiss/ucomisd xmm1, xmm2 ; sygnalizuje błąd tylko dla SNaN
Przykład
comiss xmm0, xmm1 ; porównuje dwa floaty
jb etykieta ; robi skok jeżeli xmm0[0] < xmm1[0]
Porównania ustawiające maskę bitową
Gdy porównujemy odpowiadające elementy dwóch wektorów to wyniki porównania mogą się różnić dla poszczególnych par elementów. Zamiast pojedynczego wyniku otrzymujemy na danej pozycji maskę bitową 111...11
gd porównanie zwróci prawdę lub 00...00
w przeciwnym wypadku.
Liczby zmiennoprzecinkowe
cmpps/cmppd a, b, XX ; porównuje elementy wektorów a i b predykatem XX, maskę zapisuje w a
cmpss/cmpsd a, b, XX ; porównuje skalarnie elementy a[0] i b[0] predykatem XX
vcmp** r, a, b, XX ; porównuje wektory a i b podobnie jak cmp** ale maskę zapisuje w r
Odpowiadające elementy dwóch wektorów (traktowane jako liczby float lub double) są porównywane i jeżeli zachodzi relacja a[i] XX b[i]
to wynikiem na danej pozycji jest maska 111...11
w przeciwnym wypadku 00...00
. W przypadku instrukcji skalarnych porównywane są elementy z indeksem 0.
a[i] = (a[i] XX b[i]) ? 0xFF..FF : 0x00..00;
Najczęściej wykorzystywane predykaty i odpowiadające im pseoudoinstrukcje (są zamieniane przez kompilator na instrukcję z odpowiednią stałą XX).
XX | predykat | pseudoinstrucja |
---|---|---|
0 | = | CMPEQ** |
1 | < | CMPLT** |
2 | <= | CMPLE** |
3 | isnan | CMPUNORD** |
4 | != | CMPNEQ** |
5 | > | CMPNLT** |
6 | >= | CMPNLE** |
7 | !isnan | CMPORD** |
Przykład. Testujemy, używając predykatu <
, czy odpowiednie elementy wektora xmm0
, traktowane jako liczby float, są mniejsze od odpowiednich elementów wektora xmm1
. Przykładowo dla poniższych danych otrzymamy maskę
cmpltps xmm0, xmm1 ; równoznaczne cmpps xmm0, xmm1, 1
xmm0 : | 2.0 | -4.3 | 36.4 | 12.1 |
xmm1 : | 7.0 | -4.3 | 1.5 | 12.2 |
= = = =
xmm0 : |111..1111|000..0000|000..0000|111..1111| ; (maska)
Liczby całkowite
Dla predykatu XX (EQ, GT lub LT) i tablicy liczb całkowitych ze znakiem o rozmiarze * (B=1, W=2, D=4, Q=8) wynikiem jest maska zawierająca jedynki dla tych elementów dla których predykat zwraca prawdę.
pcmpXX* a, b ; a[i] = (a[i] XX b[i])? 11...11 : 00...00
vpcmpXX* w, a, b ; w[i] = (a[i] XX b[i])? 11...11 : 00...00
Przykład: w xmm1, xmm2 mamy ciągi 16 znaków (jednobajtowe = rozmiar B), chemy porównać je predykatem < (LT) wiec stosujemy instrukcję PCMPLTB xmm1, xmm2
.
pcmpltb xmm1, xmm2
xmm1 : A B C D E F G H I J K L M N O P
xmm2 : A A A F F F O O O O O O O O O O
= = = = = = = = = = = = = = = = =
xmm1 : 000000FFFF00FFFFFFFFFFFFFFFF0000 ; (maska)
Eliminowanie skoków z wykorzystaniem masek
- Jeżeli dla elementów wektora v w zależności od spełnienia przez element predykatu
Pred
chcemy przeprowadzić różne obliczenia, których wyniki to odpowiednio Vt lub Vf. To nie pasuje do modelu SIMD. - Chcemy uniknąć instrukcji warunkowej, bo ona wymusza wykonanie skalarne.
Rozwiązanie:
- Tworzymy maskę bitową
M
dla predykatuPred
i wektorav
(ma jednyki na tych pozycjach gdzie spełniony jest predykat). - Przeprowadzamy oba obliczenia dla całych wektorów otrzymująć wektory wyników
Vt
iVf
. - Łączymy te wyniki wykorzystując maskę M i operacje bitowy
( Vt and M ) or ( Vf and !M )
Przykład
; void change(const char * x){
; for(int i=0; i<16; ++i)
; if(x[i] <= 'M') x[i] = '*';
; }
section .text
change:
movdqu xmm0, [rdi] ; ładujemy przekazany łańcuch tekstowy
movdqu xmm1, [const40] ; wektor stałych 40
movdqa xmm2, xmm0 ;
pcmpgtb xmm2, xmm1 ; xmm2 zawiera maske
movdqa xmm1, [star]
pand xmm0, xmm2 ; zerujemy znaki mniejsze niż M
pandn xmm2, xmm1 ; ustawiamy tam gwiazdki
por xmm0, xmm2 ; sumujemy
movdqu [rdi], xmm0
ret
section .data
const40 'MMMMMMMMMMMMMMMM'
star '****************'
Maskowane przesyłanie danych
Aby wyeliminować skoki możemy też wykorzystać maskowane przesyłanie danych
; Odczyt
vmaskmovps/pd xmm0, xmm1, [mem] ; wczytuje spod adresu `mem` te elementy wektora `xmm0`
; dla których w masce `xmm1` ustawione są jedynki,
; pozostałe elementy zeruje.
; Zapis
vmaskmovps/pd [mem], xmm1, xmm0 ; zapisuje pod adres `mem` te elementy wektora `xmm0`
; dla których w masce `xmm1` ustawione są jedynki,
; pozostałe elementy pozostają niezmienione.