11 sierpień 2009

SSE w klasie - #2

Po omówieniu podstaw związanych z programowaniem przy użyciu instrukcji SIMD, czas przedstawić nieco bardziej użyteczne informacje. Dzisiejszym daniem głównym więc, będzie niewielka (rozwijana w kolejnych częściach mini kursu) klasa czterowymiarowego wektora. Życzę smacznego i zapraszam do lektury :).

Na początku zaczniemy od przygotowania składników. Dziś zamiast wyrównanej do szesnastu bajtów tablicy typu float, skorzystamy ze struktury:

__declspec(align(16))
struct VectorData{
    float x,y,z,w;
};

Dla instrukcji SSE liczy się tak naprawdę odpowiednio przygotowany skrawek pamięci – w jaki sposób zostanie on zaalokowany (czy to będzie tablica czy struktura) zależy tylko od preferencji programisty.

Mając wszystko przygotowane, możemy przejść do przyrządzania dania głównego. Na pierwszy ogień pójdzie więc konstruktor domyślny, w którym to będziemy zerować współrzędne wektora.
Samo zerowanie jest oczywiście sprawą trywialną, choć początkowo może przysporzyć nieco problemów.
Tradycyjnie, można to zrobić na dwa sposoby:
  • “manualne” przypisanie zer
  • skorzystanie z instrukcji memset (nagłówek cstring lub string.h)
Skorzystanie z memset powinno nieco przyśpieszyć tą prostą operację, ponieważ nie odwołujemy się do koprocesora, tylko operujemy danymi bezpośrednio w pamięci. Mimo iż w konstruktorze nie potrzebujemy wyjątkowej wydajności (jest on wykonywany zazwyczaj tylko raz), to jednak wykorzystamy trochę mocy :).
Otóż i niezwykle wyrafinowana treść naszej funkcji: 
_asm{
    mov esi,this
    movaps xmm0,[esi]
    xorps xmm0,xmm0
    movaps [esi],xmm0
}
Jak widzimy, są tu dwie nowe rzeczy. Pojawił się wskaźnik this oraz instrukcja xorps.
Wskaźnik this używany jest by dostać się to struktury przechowującej wektor. Zadziała to jednak tylko wtedy gdy zmienna ta będzie pierwszym polem klasy. Wówczas adres na który wskazuje this jest równoznaczny z adresem owego pola.
W przeciwnym wypadku, pobranie adresu trzeba będzie wykonać “ręcznie”:
void *p = &m_vecData

_asm{
    mov esi,p
    //...
}
Cóż takiego robi instrukcja xorps? Jest to nic innego jak zwykły xor, w wersji dla SSE, działający na poszczególnych elementach naszego wektora. Wykonując tą operację na dwóch takich samych wektorach, zerujemy je. Jest to znacznie szybsze, niż “statyczne” przypisanie wartości zero do każdej ze współrzędnych. Mając konstruktor domyślny, możemy stworzyć konstruktor kopiujący oraz operator przypisania. Praktycznie rzecz ujmując będą to dwie identyczne funkcje. Ich działanie będzie się tylko ograniczać do kopiowania wartości pomiędzy rejestrami xmm. Spójrzmy więc jak to wygląda:
_asm{
    mov edi,vec
    mov esi,this

    movaps xmm0,[esi]
    movaps xmm1,[edi]

    movaps xmm0,xmm1

    movaps [esi],xmm0
}
Zakładam, że vec, to stała referencja na obiekt naszej klasy, przekazana do konstruktora kopiującego bądź operatora przypisania.
Działanie, jak już wspominałem, jest bardzo proste. Rejestry edi i esi przechowują wskaźniki na nasze obiekty, które następnie (za pomocą movaps) kopiowane są do odpowiednich rejestrów SSE – xmm0 dla naszego wektora oraz xmm1 dla wektora przekazanego w parametrze. Przypisanie wektorów to po prostu skopiowanie zawartości xmm1 do xmm0. Ostatnia instrukcja zapisuje wynik w this.

Na koniec omówimy sobie operator new i delete, ponieważ jak się okazuje, domyślne operatory nie będą kompatybilne z naszą klasą wektora.
W podstawowym ujęciu, instrukcje SSE dzielimy na dwie kategorie: operujące na wyrównanych i niewyrównanych danych. Gdy operujemy na danych niewyrównanych, działanie SSE jest nieco wolniejsze, ponieważ potrzeba więcej cykli by dostać się do odpowiedniej komórki pamięci. Przykładem tego typu instrukcji może być movups – jest to odpowiednik movaps (u – unaligned, a - aligned), tylko, że tablica lub struktura, do której chcemy uzyskać dostęp, nie musi być wyrównana do granicy szesnastu bajtów.

Cóż więc wspólnego mają z tym operatory new i delete? To, że przydzielają one niewyrównane do odpowiadającej nam wartości, kawałki pamięci. Należy zatem operatory te przeciążyć. Na szczęście jest to operacja równie trywialna, jak te, które zaprezentowałem powyżej. Jeden ze sposobów zrobienia tego, opisał Netrix, mi jednak udało się, znaleźć znacznie prostsze rozwiązanie. Okazuje się bowiem, że istnieją funkcje które robią to za nas: _aligned_malloc i _aligned_free. Zewnętrznie działają tak samo jak standardowe wersje malloc i free, więc ich użytkowanie nie powinno sprawiać większego problemu.

Implementację dodawania i odejmowania zostawiam jako zadanie domowe :). Instrukcje, które je wykonują omówiłem w poprzedniej części kursiku więc nie powinno to stanowić większego problemu.

W załączniku znajduje się kod do dzisiejszego kursiku. Dodałem także metodę zerującą wektor.

05 sierpień 2009

Najsławniejsi po polsku

Przeglądając ostatnio serwisy z newsami dotyczącymi tematyki IT oraz ogólnie pojętego programowania, natrafiłem na dość interesującą informację. Powstał bowiem blog, którego autor postawił sobie za cel, tłumaczenie notek ze stron domowych takich sław jak Joel Spolsky lub Jeff Atwood. Jest to z pewnością bardzo ciekawa inicjatywa, szczególnie dla osób, które jeszcze z angielskim mają lekkie problemy :)

Adres strony: http://www.devblogi.pl.