SSE to dodatkowe rozkazy rozpoznawane przez mikroprocesory firmy Intel oraz kompatybilnych, które pozwalają znacznie szybciej wykonywać obliczenia matematyczne, szczególnie te wykorzystywane w dziedzinie multimediów, co przekłada się na zwiększenie efektywności działania m.in. gier komputerowych, programów graficznych, muzycznych, kodowania filmów i muzyki.
SSE (ang.Streaming SIMD Extensions) jest nazwą zestawu instrukcji wprowadzonego w 1999 roku po raz pierwszy w procesorachPentium III firmy Intel. SSE daje przede wszystkim możliwość wykonywania działań zmiennoprzecinkowych na 4-elementowych wektorach liczb pojedynczej precyzji (48 rozkazów). Ponadto wprowadzono jedenaście nowych rozkazów stałoprzecinkowych w zestawie MMX, a także dano możliwość wskazywania, które dane powinny znaleźć się w pamięci podręcznej.
SSE to również zmiany w architekturze procesora: dodano 8 rejestrów XMM o rozmiarze 128 bitów oraz 32-bitowy rejestr kontrolny MXCSR; w 64-bitowych wersjach procesorów (AMD64, EM64T) dostępne jest jeszcze 8 dodatkowych rejestrów XMM. Rejestry 128-bitowe, na zawartości których wykonywana jest większość rozkazów SSE (nazywane w asemblerzexmm0, xmm1, ..., xmm15), stanowią zupełnie odrębne komórki pamięci – w odróżnieniu od rejestrów MMX nie zostały zamapowane na inne rejestry.
Typy danych
SSE wprowadza nowy typ danych: 4-elementowy wektor liczb zmiennoprzecinkowych pojedynczej precyzji (ang. 128-bit packed single-precision floating-point); liczba zmiennoprzecinkowa ma rozmiar 32 bitów. Poza tym wykorzystuje typy wektorowe zdefiniowane w MMX.
Rozkazy SSE mogą wykonywać działania arytmetyczne na wektorach liczb zmiennoprzecinkowych na dwa sposoby:
packed (równoległe) – wykonując równocześnie 4 niezależne działania zmiennoprzecinkowe na odpowiadających sobie elementach wektorów;
scalar (skalarne) – wykonując działanie tylko na pierwszych elementach wektorów.
Przykład – mnożenie dwóch wektorów (rozkazem MULPS xmm0, xmm1):
Mnemoniki instrukcji SSE działających na wektorach liczb całkowitych zostały wybrane zgodnie z konwencję wprowadzoną w MMX – nazwy zaczynają się najczęściej od litery P.
Dla nazw instrukcji działających na liczbach zmiennoprzecinkowych nie wprowadzono żadnego prefiksu; jednak podobnie jak w MMX sufiks nazwy określa typ:
PS (packed single) – działanie na wektorach,
SS (scalar single) – działanie na skalarach.
Ponadto jeśli rozkazy działają na połówkach rejestrów XMM (tj. albo odnoszą się do bitów 0..63, albo 64..127), w mnemonikach rozkazu występuje litera – odpowiednio – L albo H, od angielskich słów low („niski”) i high („wysoki”).
Działania zmiennoprzecinkowe
SSE jest zgodne ze standardem IEEE-754. Możliwe jest jednak włączenie pewnych niestandardowych cech, które w niektórych przypadkach przyspieszają obliczenia.
Ad. 1. Modyfikujące rejestr SSE w sposób analogiczny jak w MMX: dla tych elementów, dla których wynik porównania jest prawdziwy wszystkie bity w rejestrze docelowym są ustawiane, gdy nieprawdziwy – zerowane. Ten sposób porównania może być zastosowany zarówno dla wektorów (rozkaz CMPPS), jak i skalarów (rozkaz CMPSS).
Przykład testowania, czy liczby są różne (rozkaz CMPNEQPS xmm0, xmm1[1]):
Ad. 2. Modyfikujące rejestr flag, dzięki czemu można sterować przepływem sterowania za pomocą rozkazów skoku warunkowego; rozpoznawane są 4 różne relacje liczb: mniejszy, większy, równy oraz unordered. Ten sposób porównania może być stosowany tylko do skalarów, przy czym dostępne są dwa rozkazy: COMISS – sygnalizujący błąd gdy wystąpi nieprawidłowa liczba zmiennoprzecinkowa QNaN lub SNaN oraz UCOMISS – sygnalizująca błąd tylko w przypadku SNaN.
Konwersje pomiędzy liczbami całkowitymi i zmiennoprzecinkowymi
#
typ źródłowy
typ docelowy
instrukcja
1
liczba całkowita
liczba zmiennoprzecinkowa
CVTSI2SS
2
para liczb całkowitych
para liczb zmiennoprzecinkowych
CVTPI2PS
3
liczba zmiennoprzecinkowa
liczba całkowita
CVTSS2SI
4
CVTTPS2PI
5
para liczb zmiennoprzecinkowych
para liczb całkowitych
CVTPS2PI
6
CVTTPS2PI
Uwagi:
Liczby całkowita, tj. 32-bitowe liczby ze znakiem
Metoda zaokrąglania w większości rozkazów jest ustawiana w rejestrze kontrolnym MXCSR, wyjątkiem są rozkazy CVTTPS2PI i CVTTSS2SI dla których zawsze trybem zaokrąglanie jest ucinanie (ang. chop, truncate).
Wyniki zapisywane są do najmłodszego albo dwóch najmłodszych elementów wektora docelowego, zaś pozostałe elementy nie są zmieniane.
Rozmieszczenie elementów w wektorze
Rozkazy SHUFPS, UNPCKLPS, UNPCKHPS umożliwiają różnorakie rozmieszczenie („wymieszanie”) elementów, np. odwrócenie kolejności elementów w wektorze.
UNPCKLPS, UNPCKHPS
Rozkazy ustawia na przemian 2 elementy z obu wektorów. UNPCKLPS bierze dwa młodsze elementy (tj. o indeksach 0 i 1), natomiast UNPCKHPS dwa starsze (indeksy 2 i 3). Rozkaz UNPCKLPS xmm1, xmm2 wykonuje:
3 2 1 0
+-----+-----+-----+-----+
xmm1 = | d | c | b | a |
+-----+-----+-----+-----+
+-----+-----+-----+-----+
xmm2 = | h | g | f | e |
+-----+-----+-----+-----+
Wynik UNPCKLPS:
+-----+-----+-----+-----+
xmm1 = | f | b | e | a |
+-----+-----+-----+-----+
Wynik UNPCKHPS:
+-----+-----+-----+-----+
xmm1 = | h | d | g | c |
+-----+-----+-----+-----+
SHUFPS
Rozkaz bardziej ogólny niż UNPCKxPS, umożliwia wskazanie dowolnych indeksów z wektorów źródłowych za pomocą trzeciego argumentu (stałej natychmiastowej), w którym na każdych kolejnych dwóch bitach zapisane są 4 indeksy. Rozkazowi SHUFPS xmm1, xmm2, imm8 odpowiada:
{ pobranie indeksów }
index1_0 := (imm8 AND 00000011b)
index1_1 := (imm8 AND 00001100b) SHR 2
index2_0 := (imm8 AND 00110000b) SHR 4
index2_1 := (imm8 AND 11000000b) SHR 6
{ rozmieszczenie elementów }
temp[0] := xmm1[index1_0]
temp[1] := xmm1[index1_1]
temp[2] := xmm2[index2_0]
temp[3] := xmm2[index2_1]
xmm1 := temp
MXCSR – rejestr kontrolny/statusu
Rejestr MXCSR przechowuje:
Ustawienia operacji zmiennoprzecinkowych:
sposób zaokrąglanie wyniku:
do najbliższej liczby całkowitej
zaokrąglanie w stronę plus nieskończoności
zaokrąglanie w stronę minus nieskończoności
ucinanie (zaokrąglanie w stronę zera)
flaga flush-to-zero – jeśli ustawiona w przypadku niedomiaru zamiast zgłaszania wyjątku, zapisywana jest liczba zero; działanie nie jest zgodne ze standardem, ale powoduje przyspieszenie programów
Maski włączające zgłaszanie wyjątków przy błędach; wykrywane błędy:
niewłaściwe argumenty (np. pierwiastkowanie ujemnej liczby),
dzielenie przez zero,
nadmiar (wynik jest zbyt duży),
niedomiar (wynikiem jest liczba nie znormalizowana),
niedokładny wynik (wynik nie może być dokładnie reprezentowany).
Flagi wskazujące rodzaj błędu – ustawiane automatycznie przez procesor, niezależnie od tego, czy dany błąd jest zgłaszany, czy nie; muszą zostać wyzerowane programowo (zwykle w procedurze obsługi wyjątków SSE).
Przesłania liczb zmiennoprzecinkowych między rejestrami i pamięcią
Rozkazy działają na wektorach liczb zmiennoprzecinkowych (4 elementy).
MOVAPS, MOVUPS
Przesłanie 4 liczb zmiennoprzecinkowych pomiędzy rejestrem XMM a pamięcią lub innym rejestrem XMM:
MOVAPS – wymaga, by adres pamięci był wyrównany do granicy 16 bajtów, tj. jego 4 najmłodsze bity muszą być równe zero – w przeciwnym przypadku zgłaszany jest błąd.
MOVUPS – nie nakłada takich ograniczeń, ale odczyt danych niewyrównanych jest zwykle wolniejszy.
MOVSS
Przesłanie jednej liczby zmiennoprzecinkowej pomiędzy rejestrem XMM a pamięcią lub innym rejestrem XMM. Rozkaz działa na elemencie 0 rejestrów XMM: przy przesłaniach z rejestru do rejestru tylko on jest zmieniany, przy przesłaniu z pamięci do rejestru zerowane są pozostałe elementy.
MOVLPS, MOVHPS
Przesłanie 2 liczb zmiennoprzecinkowych pomiędzy rejestrem XMM i pamięcią. Rozkaz MOVLPS działa na elementach 0 i 1 rejestru XMM, natomiast MOVHPS na elementach 2 i 3.
MOVLHPS, MOVHLPS
Przesłanie między rejestrami 64-bitów (2 liczb zmiennoprzecinkowych)
MOVLHPS – zapisanie elementów 0 i 1 rejestru źródłowego na pozycjach 2 i 3 rejestru docelowego;
MOVHLPS – zapisanie elementów 2 i 3 rejestru źródłowego na pozycjach 0 i 1 rejestru docelowego.
MOVMSKPS
Utworzenie 4-bitowej maski z najstarszych bitów każdej z liczb (tj. z bitów znaku) i zapisanie jej do rejestru ogólnego przeznaczenia x86 (EAX, EBX itd.).
Pamięć podręczna
W SSE dostępne są trzy grupy rozkazów odnoszące się do pamięci podręcznej.
Pobranie danych „bliżej” procesora
Rozkazy PREFETCH (PREFETCHT0, PREFETCHT1, PREFETCHT2, PREFETCHNTA) są rodzajami podpowiedzi (ang. hint) dla procesora, wskazującymi, że obszar pamięci powinien znaleźć się wyżej w hierarchii pamięci podręcznej: poziom 1 znajduje się najbliżej procesora, poziom 2 dalej itd. Im „bliżej” procesora znajdują się dane, tym mniejszy jest czas oczekiwania na nie; np. jeśli dane są już w pamięci podręcznej pierwszego poziomu, prawie w ogóle nie trzeba czekać, w przeciwnym razie czas oczekiwania może wynieść kilkanaście, a nawet kilkadziesiąt lub kilkaset cykli maszynowych[3].
Programista czy kompilator wie lepiej kiedy i które dane wykorzystuje – za pomocą rozkazów PREFETCH może odpowiednio wcześniej powiadomić procesor o zapotrzebowaniu i uniknąć tym samym oczekiwania, kiedy dane będą już potrzebne.
Należy zauważyć, że procesor może owych „podpowiedzi” w ogóle nie uwzględnić.
Trwały zapis (ang. non-temporal)
Pamięć podręczna służy m.in. do szybkiego sięgania po ostatnio zapisane informacje. Jednak zauważono, że pewnych przypadkach dane zapisywane z rejestrów do pamięci nie są więcej używane i dlatego nie ma potrzeby, aby zapisywać je w pamięci podręcznej (oraz marnować przy okazji jej ograniczone zasoby).
W SSE wprowadzono trzy rozkazy pozwalające przesłać dane do pamięci z pominięciem pamięci podręcznej:
MOVNTQ – zapis zawartości rejestru MMX
MOVNTPS – zapis zawartości rejestru SSE
MASKMOVQ – zapis wybranych bajtów z rejestru MMX
W przypadku innych przesłań do pamięci, procesor może zakładać, że wszystkie zapisy do pamięci są tymczasowe (ang. temporal) i w ogóle nie uaktualniać zawartości pamięci głównej. Za pomocą rozkazu SFENCE wymusza się synchronizację – patrz bariera pamięci.
64-bitowe rozkazy całkowitoliczbowe
Operandami dodatkowych rozkazów całkowitoliczbowych są tylko rejestry MMX; w SSE2 pojawiła się już możliwość wykorzystania również rejestrów SSE.
Rozkazy:
PAVGB, PAVGW – średnia bajtów/słów bez znaku (tutaj: słowo ma 16-bitów)
PMAXUB, PMINUB – wybranie bajtów bez znaku o maksymalnej/minimalnej wartości
PMAXSW, PMINSW – wybranie słów ze znakiem o maksymalnej/minimalnej wartości
PMOVMSKB – utworzenie maski bitowej z najstarszych bitów wszystkich bajtów
PMULHUW – starsze słowo z wyniku mnożenia słów bez znaku
PSADBW – suma modułów różnicy bajtów (tj. czyli odległość w metryce manhattan)
PEXTRW, PINSRW – pobranie/wstawienie dowolnego słowa wektora
dodatkowe rozkazy wektorowe działające zarówno na liczbach całkowitych jak zmiennoprzecinkowych,
wprowadzenie rozkazów trój- i czteroargumentowych, w który jeden z argumentów jest docelowy (rozwiązanie z architektury RISC) – dotychczas rozkazy były 2-argumentowe, z czego jeden był równocześnie docelowy i jeśli jego wartość była potrzebna w dalszej części obliczeń, należało go zapamiętać – w rozkazach 3- i 4-argumentowych takiego problemu nie ma,
dodanie nowych, 256-bitowych rejestrów: część istniejących rozkazów SSE, SSE2, SSE3 i SSSE3, głównie zmiennoprzecinkowych może wykonywać działania na tych rejestrach,
kilka rozkazów wektorowych działających wyłącznie na 256-bitowych rejestrach,
część istniejących rozkazów może być wykonywana wariantach 3-argumentowych (jak w SSE5),
rozkazy 4-argumentowe pozwalają akumulować wyniki mnożenia na liczbach zmiennoprzecinkowych według schematu
zwiększono z 8 do 32 liczbę relacji, które można sprawdzić rozkazami porównania (CMPPS, CMPPD),
↑Rozkazy CMPPS/CMPSS są trójargumentowe: dwa pierwsze argumenty to porównywane wektory, trzeci argument to stała określająca testowaną relację. Jednak Intel proponuje, aby w asemblerze dostępne były dwurgumentowe pseduorozkazy, w których rodzaj relacji zapisany będzie w mnemoniku – np. CMPNEQPS xmm0, xmm1 odpowiada CMPPS xmm0, xmm1, 4 (4 – kod dla relacji „różny”, ang. Not EQual).
↑Relacja unordered jest prawdziwa, gdy argumentów nie można porównać, ponieważ przynajmniej jeden z nich jest nie-liczbą (NaN) lub nie reprezentuje prawidłowej liczby zmiennoprzecinkowej.
↑Orientacyjne wartości: jeśli dane są w L1 – 2-3 cykle, w L2 – rzędu 10 cykli, w pamięci głównej – rzędu 100–200 cykli.