Skocz do: nawigacji, wyszukiwania

MICLAB:Manualna wektoryzacja


Środowisko i modele programowania

dr inż. Łukasz Szustak, Politechnika Częstochowska, IITiS
mgr inż. Kamil Halbiniak, Politechnika Częstochowska, IITiS



Manualna wektoryzacja obliczeń


Instrukcje wektorowe mogą zostać również zaimplementowane bezpośrednio w kodzie źródłowym aplikacji. Rozwiązanie to zapewnia lepszą kontrolę nad sposobem wykonywania obliczeń, jednakże zazwyczaj okazuje się trudniejsze w użyciu oraz bardziej czasochłonne. Uzyskanie wysokiej wydajności obliczeń, podobnie jak w przypadku automatycznej wektoryzacji wymaga odpowiedniego dopasowania adresów w pamięci. Dodatkowo proces manualnej wektoryzacji wymaga, aby rozmiar tablicy, która wykorzystywana jest w obliczeniach był wielokrotnością rozmiaru wektora, co często wiąże się z nadmiarowymi obliczeniami.


Ograniczenie dla manualnej wektoryzacji stanowi przede wszystkim różnorodność architektur, co może powodować problemy w kompilacji oraz uruchomieniu aplikacji na różnych platformach. Przykładem tego może być próba uruchomienia programu dedykowanego dla koprocesora Intel Xeon Phi na procesorze hosta. Poprawne zastosowanie wektoryzacji w takim przypadku wymaga restrukturyzacji obszarów kodu źródłowego, w których zaimplementowano instrukcje wektorowe. Ręczna implementacja technik wektoryzacji w programie możliwa jest przy użyciu:

  • instrukcji asemblerowych;
  • instrukcji intrinsic;
  • biblioteki klas wektorowych.


Instrukcje intrinsic są funkcjami języków C lub C++ będącymi odpowiednikami niskopoziomowych instrukcji wektorowych [12]. Funkcje te wykonują operacje na typach danych reprezentujących 512-bitowe rejestry koprocesora. Typy wektorowe dedykowane dla architektury Intel MIC wraz instrukcjami intrinsic zdefiniowane zostały w pliku nagłówkowym immintrin.h. Plik ten zawiera następujące typy danych:

  • __m512 - wektor 16 elementów zmiennoprzecinkowych pojedynczej precyzji;
  • __m512d - wektor 8 elementów zmiennoprzecinkowych podwójnej precyzji;
  • __m512i - wektor 16 elementów 32-bitowych, 8 elementów 64-bitowychh typu całkowitego
Instrukcje intrinsic dedykowane dla architektury Intel MIC pozwalają na wykonywanie między innymi operacji arytmetycznych, logicznych oraz konwersji. Oferują one również możliwość wykonywania bardziej zaawansowanych operacji, taki jak zapis danych z pominięciem hierarchii pamięci, pobieranie danych z wyprzedzeniem oraz tasowanie danych. Przykładowy kod ilustrujący manualną wektoryzację przy pomocy instrukcji intriscis przedstawiono na Listingu 1.


for(int i=0; i<n; i+=8) 
{
   __m512d vecA = _mm512_load_pd(A+i);
   __m512d vecB = _mm512_load_pd(B+i);
   vecA = _mm512_add_pd(vecA, vecB);
   _mm512_store_pd(C+i, vecA);
}
Listing 1. Manualna wektoryzacja obliczeń przy pomocy intrukcji intrinsic


Listing 1 przedstawia przykład dodawania elementów dwóch tablic. Dane przechowywane w tablicy ładowane są do 8 elementowego wektora przechowującego elementy zmiennoprzecinkowe podwójnej precyzji, który reprezentowany jest przez typ __m512d. Następnie wartości załadowane do dwóch wektorów dodawane są do siebie, po czym zapisywane są w tablicy przechowującej wartości wynikowe. W zaprezentowanym przykładzie wykorzystane zostały instrukcje intrinsic operujące na dopasowanych danych. W obliczeniach wekorowych gdzie dane nie są odpowiednio wyrównane w pamięci, należy wykorzystać kombinację instrukcji loadu oraz storeu. Jednakże, w architekturze Intel MIC instrukcje te nie występują. Wobec tego, w przypadku operacji na danych niedopasowanych w pamięci programista zmuszony jest do implementacji własnych rozwiązań.


Biblioteka klas wektorowych języka C++ dostarczana jest przez kompilatory firmy Intel. Zawiera ona wektory reprezentowane jako klasy z przeciążonymi operatorami pozwalającymi wykonywać na nich operacje w kategoriach instrukcji SIMD. Zestaw klas dedykowanych dla architektury Intel MIC zdefiniowany został w pliku nagłówkowym micvec.h. Zawiera on następujące typy wektorowe:

  • F64vec8 - wektor 8 elementów zmiennoprzecinkowych podwójnej precyzji;
  • F32vec16 - wektor 16 elementów zmiennoprzecinkowych pojedynczej precyzji;
  • M512 - wektor przechowujący 1 o rozmiarze 512-bitów;
  • I64vec8 - wektor 8 elementów typu całkowitego o rozmiarze 64-bitów;
  • I32vec16 - wektor 16 elementów typu całkowitego o rozmiarze 32-bitów;
  • Is32vec16 - wektor 16 elementów typu całkowitego ze znakiem o rozmiarze 32-bitów;
  • Iu32vec16 - wektor 16 elementów typu całkowitego bez znaku o rozmiarze 32-bitów.
Nazwa klasy uzależniona jest od typu przechowywanych danych, ich rozmiaru oraz liczby elementów. Wykonywanie operacji na wektorach sprowadza się jedynie do zastosowania tradycyjnych operatorów arytmetycznych (w odróżnieniu do instrukcji intrinsic, gdzie należy wykorzystać dedykowane funkcje). Upraszcza to niewątpliwie proces implemetacji obliczeń wektorowych, jednocześnie czyniąc kod źródłowy bardziej przejrzystym. Jednakże, programista ma ograniczoną kontrolę nad wykorzystywanymi instrukcjami SIMD. Przykład ręcznej wektoryzacji obliczeń przy pomocy klas języka C++ przedstawiono na Listingu 2.


for(int i=0; i<n; i+=4) 
{
   F32vec16 *Avec = (F32vec16*)(A+i);
   F32vec16 *Bvec = (F32vec16*)(B+i);
   F32vec16 *Cvec = (F32vec16*)(C+i);
   *Cvec = *Avec + *Bvec;
}
Listing 2. Manualna wektoryzacja obliczeń przy pomocy klas języka C+


Przedstawiony na Listingu 2 kod programu wykonuje dodawanie elementów dwóch tablic. Dane ładowane są do wektora typu F32vec16, który przechowuje 16 elementów zmiennoprzecinkowych pojedynczej precyzji. Następnie wartości znajdujące się w dwóch wektorach są do siebie dodawane, po czym zapisywane do pamięci przy pomocy operatora przypisania.



< Automatyczna wektoryzacja obliczeń

Raport o wektoryzacji >